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

import com.intellij.java.syntax.parser.JavaKeywords;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.GeneratedMarkerVisitor;
import com.intellij.psi.impl.source.DummyHolder;
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyFileType;
import org.jetbrains.plugins.groovy.config.GroovyConfigUtils;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocComment;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocMemberReference;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocReferenceElement;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocTag;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.GrLambdaExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifier;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationNameValuePair;
import org.jetbrains.plugins.groovy.lang.psi.api.signatures.GrSignature;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.*;
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.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrCaseSection;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstant;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.GrTopStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrClosureParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.ErrorUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static org.jetbrains.plugins.groovy.lang.psi.GroovyElementTypes.*;

@SuppressWarnings("ConstantConditions")
public final class GroovyPsiElementFactoryImpl extends GroovyPsiElementFactory {
  private static final Logger LOG = Logger.getInstance(GroovyPsiElementFactoryImpl.class);

  private final Project myProject;
  private final PsiManager myManager;

  public GroovyPsiElementFactoryImpl(Project project) {
    myProject = project;
    myManager = PsiManager.getInstance(project);
  }

  @Override
  public @NotNull PsiElement createReferenceNameFromText(@NotNull String refName) {
    GroovyFileBase file = createGroovyFileChecked("a." + refName);
    GrTopStatement statement = file.getTopStatements()[0];
    if (!(statement instanceof GrReferenceExpression)) {
      throw new IncorrectOperationException("Incorrect reference name: " + refName);
    }
    final PsiElement element = ((GrReferenceExpression)statement).getReferenceNameElement();
    if (element == null) {
      throw new IncorrectOperationException("Incorrect reference name: " + refName);
    }
    return element;
  }

  @Override
  public @NotNull PsiElement createDocMemberReferenceNameFromText(@NotNull String idText) {
    GrDocMemberReference reference = createDocMemberReferenceFromText("Foo", idText);
    LOG.assertTrue(reference != null, idText);
    return reference.getReferenceNameElement();
  }

  @Override
  public @NotNull GrDocMemberReference createDocMemberReferenceFromText(@NotNull String className, @NotNull String text) {
    PsiFile file = createGroovyFileChecked("/** @see " + className + "#" + text + " */");
    PsiElement element = file.getFirstChild();
    assert element instanceof GrDocComment;
    GrDocTag tag = PsiTreeUtil.getChildOfType(element, GrDocTag.class);
    assert tag != null : "Doc tag points to null";
    return PsiTreeUtil.getChildOfType(tag, GrDocMemberReference.class);
  }

  @Override
  public @NotNull GrDocReferenceElement createDocReferenceElementFromFQN(@NotNull String qName) {
    PsiFile file = createGroovyFileChecked("/** @see " + qName + " */");
    PsiElement element = file.getFirstChild();
    assert element instanceof GrDocComment;
    GrDocTag tag = PsiTreeUtil.getChildOfType(element, GrDocTag.class);
    assert tag != null : "Doc tag points to null";
    return PsiTreeUtil.getChildOfType(tag, GrDocReferenceElement.class);
  }

  @Override
  public @NotNull GrCodeReferenceElement createCodeReference(@NotNull String text, @Nullable PsiElement context) {
    return createElementFromText(text, context, CODE_REFERENCE, GrCodeReferenceElement.class);
  }

  @Override
  public @NotNull GrReferenceExpression createReferenceExpressionFromText(@NotNull String idText) {
    GroovyFileBase file = createGroovyFileChecked(idText);
    final GrTopStatement[] statements = file.getTopStatements();
    if (!(statements.length == 1 && statements[0] instanceof GrReferenceExpression)) throw new IncorrectOperationException(idText);
    return (GrReferenceExpression) statements[0];
  }

  @Override
  public @NotNull GrReferenceExpression createReferenceExpressionFromText(@NotNull String idText, PsiElement context) {
    GroovyFile file = createGroovyFileChecked(idText, false, context);
    GrTopStatement[] statements = file.getTopStatements();

    if (statements.length != 1) throw new IncorrectOperationException("refText: " + idText);
    if (!(statements[0] instanceof GrReferenceExpression)) throw new IncorrectOperationException("refText: " + idText);

    return (GrReferenceExpression)statements[0];
  }

  @Override
  public @NotNull GrReferenceExpression createReferenceElementForClass(@NotNull PsiClass aClass) {
    final String text;
    if (aClass instanceof PsiAnonymousClass) {
      text = ((PsiAnonymousClass)aClass).getBaseClassType().getPresentableText();
    }
    else {
      text = aClass.getName();
    }
    return createReferenceExpressionFromText(text);
  }

  @Override
  public @NotNull GrExpression createExpressionFromText(@NotNull String text, PsiElement context) {
    GroovyFile file = createGroovyFile(text, false, context);
    GrTopStatement[] topStatements = file.getTopStatements();
    if (topStatements.length == 0 || !(topStatements[0] instanceof GrExpression)) {
      throw new IncorrectOperationException("incorrect expression = '" + text + "'");
    }
    return (GrExpression) topStatements[0];
  }

  @Override
  public @NotNull GrCodeReferenceElement createReferenceElementByType(PsiClassType type) {
    if (type instanceof GrClassReferenceType) {
      return ((GrClassReferenceType)type).getReference();
    }

    final PsiClassType.ClassResolveResult resolveResult = type.resolveGenerics();
    final PsiClass refClass = resolveResult.getElement();
    assert refClass != null : type;
    return createCodeReference(type.getCanonicalText());
  }

  @Override
  public @NotNull PsiTypeParameterList createTypeParameterList() {
    return createMethodFromText("def <> void foo(){}").getTypeParameterList();
  }

  @Override
  public @NotNull PsiTypeParameter createTypeParameter(@NotNull String name, PsiClassType @NotNull [] superTypes) {
    @NlsSafe StringBuilder builder = new StringBuilder();
    builder.append("def <").append(name);
    if (superTypes.length > 1 ||
        superTypes.length == 1 && !superTypes[0].equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) {
      builder.append(" extends ");
      for (PsiClassType type : superTypes) {
        if (type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) continue;
        builder.append(type.getCanonicalText()).append(',');
      }

      builder.delete(builder.length() - 1, builder.length());
    }
    builder.append("> void foo(){}");
    try {
      return createMethodFromText(builder).getTypeParameters()[0];
    }
    catch (RuntimeException e) {
      throw new IncorrectOperationException("type parameter text: " + builder);
    }
  }

  @Override
  public @NotNull GrVariableDeclaration createVariableDeclaration(String @Nullable [] modifiers,
                                                                  @Nullable GrExpression initializer,
                                                                  @Nullable PsiType type,
                                                                  String... identifiers) {

    String initializerText;
    if (initializer != null) {
      if (initializer instanceof GrApplicationStatement &&
          !GroovyConfigUtils.getInstance().isVersionAtLeast(initializer, GroovyConfigUtils.GROOVY1_8, false)) {
        initializer = createMethodCallByAppCall((GrApplicationStatement)initializer);
      }
      assert initializer != null;
      initializerText = initializer.getText();
    }
    else {
      initializerText = null;
    }

    return createVariableDeclaration(modifiers, initializerText, type, identifiers);
  }

  @Override
  public @NotNull GrVariableDeclaration createVariableDeclaration(String @Nullable [] modifiers,
                                                                  @Nullable String initializer,
                                                                  @Nullable PsiType type,
                                                                  String... identifiers) {
    @NlsSafe StringBuilder text = writeModifiers(modifiers);

    if (type != null && type != PsiTypes.nullType()) {
      final PsiType unboxed = TypesUtil.unboxPrimitiveTypeWrapper(type);
      final String typeText = getTypeText(unboxed);
      text.append(typeText).append(" ");
    } else if (text.isEmpty()) {
      text.insert(0, "def ");
    }

    if (identifiers.length > 1 && initializer != null) {
      text.append('(');
    }

    text.append(String.join(", ", identifiers));

    if (identifiers.length > 1 && initializer != null) {
      text.append(')');
    }

    if (!StringUtil.isEmptyOrSpaces(initializer)) {
      text.append(" = ").append(initializer);
    }

    GrTopStatement[] topStatements = createGroovyFileChecked(text).getTopStatements();
    if (topStatements.length == 0 || !(topStatements[0] instanceof GrVariableDeclaration)) {
      topStatements = createGroovyFileChecked("def " + text).getTopStatements();
    }
    if (topStatements.length == 0 || !(topStatements[0] instanceof GrVariableDeclaration statement)) {
      throw new RuntimeException("Invalid arguments, text = " + text);
    }

    //todo switch-case formatting should work without this hack
    CodeEditUtil.markToReformatBefore(statement.getNode().findLeafElementAt(0), true);
    return statement;
  }

  @Override
  public @NotNull GrEnumConstant createEnumConstantFromText(@NotNull String text) {
    GroovyFile file = createGroovyFileChecked("enum E{" + text + "}");
    final GrEnumTypeDefinition enumClass = (GrEnumTypeDefinition)file.getClasses()[0];
    return enumClass.getEnumConstants()[0];
  }

  @Override
  public @NotNull GrVariableDeclaration createFieldDeclaration(String @NotNull [] modifiers,
                                                               @NotNull String identifier,
                                                               @Nullable GrExpression initializer,
                                                               @Nullable PsiType type) {
    final String varDeclaration = createVariableDeclaration(modifiers, initializer, type, identifier).getText();

    final GroovyFileBase file = createGroovyFileChecked("class A { " + varDeclaration + "}");
    final GrTypeDefinitionBody body = file.getTypeDefinitions()[0].getBody();
    LOG.assertTrue(body.getMemberDeclarations().length == 1 && body.getMemberDeclarations()[0] instanceof GrVariableDeclaration,
                   "ident = <" + identifier + "> initializer = " + (initializer == null ? "_null_" : ("<" + initializer.getText()) + ">"));
    return (GrVariableDeclaration) body.getMemberDeclarations()[0];
  }

  @Override
  public @NotNull GrVariableDeclaration createFieldDeclarationFromText(@NotNull String text) {
    final GroovyFile file = createGroovyFileChecked("class X{\n" + text + "\n}");
    final PsiClass psiClass = file.getClasses()[0];
    return (GrVariableDeclaration)psiClass.getFields()[0].getParent();
  }

  private static StringBuilder writeModifiers(String[] modifiers) {
    StringBuilder text = new StringBuilder();
    if (!(modifiers == null || modifiers.length == 0)) {
      for (String modifier : modifiers) {
        text.append(modifier);
        text.append(" ");
      }
    }
    return text;
  }

  private static String getTypeText(PsiType type) {
    if (!(type instanceof PsiArrayType)) {
      final String canonical = type.getCanonicalText();
      final String text = canonical != null ? canonical : type.getPresentableText();
      if (JavaKeywords.NULL.equals(text)) {
        return "";
      }
      else {
        return text;
      }
    }
    else {
      return getTypeText(((PsiArrayType)type).getComponentType()) + "[]";
    }
  }

  @Override
  public @NotNull GrTopStatement createTopElementFromText(@NotNull String text) {
    GroovyFile dummyFile = createGroovyFileChecked(text);
    final GrTopStatement[] topStatements = dummyFile.getTopStatements();
    if (topStatements.length != 1) throw new IncorrectOperationException("text = '" + text + "'");
    return topStatements[0];
  }

  @Override
  public @NotNull GrClosableBlock createClosureFromText(@NotNull String closureText, PsiElement context) throws IncorrectOperationException {
    return createElementFromText(closureText, context, CLOSURE, GrClosableBlock.class);
  }

  @Override
  public @NotNull GrLambdaExpression createLambdaFromText(@NotNull String lambdaText, PsiElement context) throws IncorrectOperationException {
    return createElementFromText(lambdaText, context, LAMBDA_EXPRESSION, GrLambdaExpression.class);
  }

  private GroovyFileImpl createDummyFile(@NotNull CharSequence text, boolean physical) {
    final String fileName = DUMMY_FILE_NAME + '.' + GroovyFileType.GROOVY_FILE_TYPE.getDefaultExtension();
    final long stamp = System.currentTimeMillis();
    final PsiFileFactory factory = PsiFileFactory.getInstance(myProject);
    return (GroovyFileImpl) factory.createFileFromText(fileName, GroovyFileType.GROOVY_FILE_TYPE, text, stamp, physical);
  }

  @Override
  public @NotNull GrParameter createParameter(@NotNull String name,
                                              @Nullable String typeText,
                                              @Nullable String initializer,
                                              @Nullable GroovyPsiElement context,
                                              String... modifiers) throws IncorrectOperationException {
    try {
      @NonNls StringBuilder fileText = new StringBuilder();
      fileText.append("def dsfsadfnbhfjks_weyripouh_huihnrecuio(");
      for (String modifier : modifiers) {
        fileText.append(modifier).append(' ');
      }
      if (StringUtil.isNotEmpty(typeText)) {
        fileText.append(typeText).append(' ');
      }
      fileText.append(name);
      if (initializer != null && !initializer.isEmpty()) {
        fileText.append(" = ").append(initializer);
      }
      fileText.append("){}");
      GroovyFile groovyFile = createGroovyFileChecked(fileText, false, context);

      ASTNode node = groovyFile.getFirstChild().getNode();
      return ((GrMethod)node.getPsi()).getParameters()[0];
    }
    catch (RuntimeException e) {
      throw new IncorrectOperationException("name = " + name + ", type = " + typeText + ", initializer = " + initializer);
    }
  }

  @Override
  public @NotNull GrTypeDefinition createTypeDefinition(@NotNull String text) throws IncorrectOperationException {
    final GroovyFileBase file = createGroovyFileChecked(text);
    final GrTypeDefinition[] classes = file.getTypeDefinitions();
    if (classes.length != 1) throw new IncorrectOperationException("Incorrect type definition text");
    return classes[0];
  }

  @Override
  public @NotNull GrTypeElement createTypeElement(@NotNull String typeText, final @Nullable PsiElement context) throws IncorrectOperationException {
    return createElementFromText(typeText, context, TYPE_ELEMENT, GrTypeElement.class);
  }

  @Override
  public @NotNull GrTypeElement createTypeElement(@NotNull PsiType type) throws IncorrectOperationException {
    final String typeText = getTypeText(type);
    if (typeText == null)
      throw new IncorrectOperationException("Cannot create type element: cannot obtain text for type");
    return createTypeElement(typeText);
  }

  @Override
  public @NotNull PsiClassType createType(@NotNull PsiClass aClass) {
    return JavaPsiFacade.getElementFactory(myProject).createType(aClass);
  }

  @Override
  public @NotNull GrParenthesizedExpression createParenthesizedExpr(@NotNull GrExpression expression, @Nullable PsiElement context) {
    return ((GrParenthesizedExpression)createExpressionFromText("(" + expression.getText() + ")", context));
  }

  @Override
  public @NotNull PsiElement createStringLiteralForReference(@NotNull String text) {
    return createLiteralFromValue(text).getFirstChild();
  }

  @Override
  public @NotNull PsiElement createModifierFromText(@NotNull String name) {
    final GroovyFileBase file = createGroovyFileChecked(name + " foo() {}");
    final GrTopStatement[] definitions = file.getTopStatements();
    if (definitions.length != 1) throw new IncorrectOperationException(name);
    return definitions[0].getFirstChild().getFirstChild();
  }

  @Override
  public @NotNull GrCodeBlock createMethodBodyFromText(@NotNull String text) {
    final GroovyFileBase file = createGroovyFileChecked("def foo () {" + text + "}");
    final GrMethod method = (GrMethod) file.getTopStatements()[0];
    return method.getBlock();
  }

  @Override
  public @NotNull GrVariableDeclaration createSimpleVariableDeclaration(@NotNull String name, @NotNull String typeText) {
    String classText;
    if (Character.isLowerCase(typeText.charAt(0))) {
      classText = "class A { def " + typeText + " " + name + "}";
    } else {
      classText = "class A { " + typeText + " " + name + "}";
    }

    GroovyFileBase file = createGroovyFileChecked(classText);
    final GrTypeDefinitionBody body = file.getTypeDefinitions()[0].getBody();
    return (GrVariableDeclaration) body.getMemberDeclarations()[0];
  }

  @Override
  public @NotNull PsiElement createDotToken(@NotNull String newDot) {
    return createReferenceExpressionFromText("a" + newDot + "b").getDotToken();
  }

  @Override
  public @NotNull GrMethod createConstructorFromText(@NotNull String constructorName,
                                                     String @Nullable [] paramTypes,
                                                     String @NotNull [] paramNames,
                                                     @Nullable String body,
                                                     @Nullable PsiElement context) {
    final CharSequence text = generateMethodText(null, constructorName, null, paramTypes, paramNames, body, true);
    return createConstructorFromText(constructorName, text, context);
  }

  @Override
  public @NotNull GrMethod createConstructorFromText(String constructorName, CharSequence constructorText, @Nullable PsiElement context) {
    GroovyFile file = createGroovyFileChecked("class " + constructorName + "{" + constructorText + "}", false, context);
    GrTypeDefinition definition = file.getTypeDefinitions()[0];

    if (definition == null) {
      throw new IncorrectOperationException("constructorName: " + constructorName + ", text: " + constructorText);
    }

    GrMethod[] methods = definition.getCodeMethods();
    if (methods.length != 1) {
      throw new IncorrectOperationException("constructorName: " + constructorName + ", text: " + constructorText);
    }

    return methods[0];
  }

  @Override
  public @NotNull GrMethod createMethodFromText(String methodText, @Nullable PsiElement context) {
    if (methodText == null) throw new IncorrectOperationException("Method text not provided");
    GroovyFile file = createGroovyFile(methodText, false, context);

    GrTopStatement[] definitions = file.getTopStatements();
    if (definitions.length != 1) {
      throw new IncorrectOperationException("Can't create method from text: '" + file.getText() + "'");
    }
    GrTopStatement definition = definitions[0];
    if (!(definition instanceof GrMethod)) {
      throw new IncorrectOperationException("Can't create method from text: '" + file.getText() + "'");
    }
    return ((GrMethod)definition);
  }

  @Override
  public @NotNull GrAnnotation createAnnotationFromText(@NotNull @NonNls String annotationText, @Nullable PsiElement context) {
    return createElementFromText(annotationText, context, ANNOTATION, GrAnnotation.class);
  }

  @Override
  public @NotNull GrAnnotationNameValuePair createAnnotationAttribute(@NotNull String text, @Nullable PsiElement context) {
    return createElementFromText(text, context, ANNOTATION_MEMBER_VALUE_PAIR, GrAnnotationNameValuePair.class);
  }

  @Override
  public @NotNull GrMethod createMethodFromSignature(@NotNull String name, @NotNull GrSignature signature) {
    StringBuilder builder = new StringBuilder(JavaKeywords.PUBLIC);
    final PsiType returnType = signature.getReturnType();
    if (returnType != null && returnType != PsiTypes.nullType()) {
      builder.append(' ');
      builder.append(returnType.getCanonicalText());
    }

    builder.append(' ').append(name).append('(');
    int i = 0;
    for (GrClosureParameter parameter : signature.getParameters()) {
      final PsiType type = parameter.getType();
      if (type != null) {
        builder.append(type.getCanonicalText());
        builder.append(' ');
      }
      builder.append('p').append(++i);
      final GrExpression initializer = parameter.getDefaultInitializer();
      if (initializer != null) {
        builder.append(" = ").append(initializer.getText());
        builder.append(", ");
      }
    }
    if (signature.getParameterCount() > 0) {
      builder.delete(builder.length() - 2, builder.length());
    }

    builder.append("){}");
    return createMethodFromText(builder);
  }

  @Override
  public @NotNull GrAnnotation createAnnotationFromText(@NotNull String annoText) {
    return createAnnotationFromText(annoText, null);
  }

  private GroovyFile createGroovyFileChecked(@NlsSafe @NotNull CharSequence idText) {
    return createGroovyFileChecked(idText, false, null);
  }

  private GroovyFile createGroovyFileChecked(@NlsSafe @NotNull CharSequence idText, boolean isPhysical, @Nullable PsiElement context) {
    final GroovyFileImpl file = createDummyFile(idText, isPhysical);
    if (ErrorUtil.containsError(file)) {
      throw new IncorrectOperationException("cannot create file from text: " + idText);
    }
    file.setContext(context);
    return file;
  }

  /**
   * use createGroovyFileChecked() inside GroovyPsiElementFactoryImpl instead of this method
   */
  @Override
  public @NotNull GroovyFile createGroovyFile(@NotNull CharSequence idText, boolean isPhysical, @Nullable PsiElement context) {
    GroovyFileImpl file = createDummyFile(idText, isPhysical);
    file.setContext(context);
    return file;
  }

  @Override
  public @NotNull PsiElement createWhiteSpace() {
    PsiFile dummyFile = createDummyFile(" ", false);
    return dummyFile.getFirstChild();
  }

  @Override
  public @NotNull PsiElement createLineTerminator(int length) {

    String text = length <= 1 ? "\n" : "";
    if (length > 1) {
      text = StringUtil.repeatSymbol('\n', length);
    }

    return createLineTerminator(text);
  }

  @Override
  public @NotNull PsiElement createLineTerminator(@NotNull String text) {
    PsiFile dummyFile = createGroovyFileChecked(text);
    PsiElement child = dummyFile.getFirstChild();
    assert child != null;
    return child;
  }

  @Override
  public @NotNull GrArgumentList createExpressionArgumentList(GrExpression... expressions) {
    @NonNls StringBuilder text = new StringBuilder();
    text.append("ven (");
    for (GrExpression expression : expressions) {
      text.append(expression.getText()).append(", ");
    }
    if (expressions.length > 0) {
      text.delete(text.length() - 2, text.length());
    }
    text.append(')');
    PsiFile file = createGroovyFileChecked(text);
    assert file.getChildren()[0] != null && (file.getChildren()[0] instanceof GrMethodCallExpression);
    return (((GrMethodCallExpression) file.getChildren()[0])).getArgumentList();
  }

  @Override
  public @NotNull GrNamedArgument createNamedArgument(final @NotNull String name, final @NotNull GrExpression expression) {
    PsiFile file = createGroovyFileChecked("foo (" + name + ":" + expression.getText() + ")");
    assert file.getChildren()[0] != null;
    GrCall call = (GrCall)file.getChildren()[0];
    return call.getArgumentList().getNamedArguments()[0];
  }

  @Override
  public @NotNull GrStatement createStatementFromText(@NotNull CharSequence text) {
    return createStatementFromText(text, null);
  }

  @Override
  public @NotNull GrStatement createStatementFromText(@NotNull CharSequence text, @Nullable PsiElement context) {
    GroovyFile file = createGroovyFileChecked(text, false, context);
    GrTopStatement[] statements = file.getTopStatements();
    if (statements.length != 1) {
      throw new IncorrectOperationException("count = " + statements.length + ", " + text);
    }
    if (!(statements[0] instanceof GrStatement)) {
      throw new IncorrectOperationException("type = " + statements[0].getClass().getName() + ", " + text);
    }
    return (GrStatement)statements[0];
  }

  @Override
  public @NotNull GrMethodCallExpression createMethodCallByAppCall(@NotNull GrApplicationStatement callExpr) {
    StringBuilder text = new StringBuilder();
    text.append(callExpr.getInvokedExpression().getText());
    text.append("(");
    final GrCommandArgumentList argumentList = callExpr.getArgumentList();
    if (argumentList != null) text.append(argumentList.getText());
    text.append(")");
    PsiFile file = createGroovyFileChecked(text);
    assert file.getChildren()[0] != null && (file.getChildren()[0] instanceof GrMethodCallExpression);
    return ((GrMethodCallExpression)file.getChildren()[0]);
  }

  @Override
  public @NotNull GrCodeReferenceElement createCodeReferenceElementFromClass(@NotNull PsiClass aClass) {
    String qualifiedName = aClass.getQualifiedName();
    if (qualifiedName == null) {
      throw new IncorrectOperationException("cannot create code reference element for class " + aClass.getText());
    }
    return createCodeReference(qualifiedName);
  }

  @Override
  public @NotNull GrReferenceExpression createThisExpression(@Nullable PsiClass psiClass) {
    final String text;
    if (psiClass == null) {
      text = "this";
    }
    else {
      final String qname = psiClass.getQualifiedName();
      if (StringUtil.isEmpty(qname)) {
        text = "this";
      }
      else {
        text = qname + ".this";
      }
    }
    return createReferenceExpressionFromText(text, psiClass);
  }

  @Override
  public @NotNull GrBlockStatement createBlockStatementFromText(@NotNull String text, @Nullable PsiElement context) {
    return createElementFromText(text, context, BLOCK_STATEMENT, GrBlockStatement.class);
  }

  @Override
  public @NotNull GrModifierList createModifierList(@NotNull CharSequence text) {
    final GrMethod method = createMethodFromText(text + " void foo()");
    return method.getModifierList();
  }

  @Override
  public @NotNull GrCaseSection createSwitchSection(@NotNull String text) {
    final GrStatement statement = createStatementFromText("switch (a) {\n" + text + "\n}");
    if (!(statement instanceof GrSwitchElement)) {
      throw new IncorrectOperationException("Cannot create switch section from text: " + text);
    }

    final GrCaseSection[] sections = ((GrSwitchElement)statement).getCaseSections();
    if (sections.length != 1) throw new IncorrectOperationException("Cannot create switch section from text: " + text);
    return sections[0];
  }

  @Override
  public @NotNull GrImportStatement createImportStatementFromText(@NotNull String qName, boolean isStatic, boolean isOnDemand, String alias) {
    return createImportStatement(qName, isStatic, isOnDemand, alias, null);
  }

  @Override
  public @NotNull GrImportStatement createImportStatementFromText(@NotNull String text) {
    PsiFile dummyFile = createGroovyFileChecked(text);
    return ((GrImportStatement) dummyFile.getFirstChild());
  }

  @Override
  public @NotNull GrImportStatement createImportStatement(@NotNull String qname,
                                                          boolean isStatic,
                                                          boolean isOnDemand,
                                                          String alias,
                                                          PsiElement context) {
    @NlsSafe StringBuilder builder = new StringBuilder();
    builder.append("import ");
    if (isStatic) {
      builder.append("static ");
    }
    builder.append(qname);
    if (isOnDemand) {
      builder.append(".*");
    }
    if (StringUtil.isNotEmpty(alias)) {
      builder.append(" as ").append(alias);
    }

    PsiFile dummyFile = createGroovyFileChecked(builder, false, context);
    return ((GrImportStatement)dummyFile.getFirstChild());
  }


  private static @NlsSafe CharSequence generateMethodText(@Nullable String modifier,
                                                          @NotNull String name,
                                                          @Nullable String type,
                                                          String @NotNull [] paramTypes,
                                                          String @NotNull [] paramNames,
                                                          @Nullable String body,
                                                          boolean isConstructor) {
    @NlsSafe StringBuilder builder = new StringBuilder();

    if (modifier != null) {
      builder.append(modifier);
      builder.append(" ");
    }

    if (!isConstructor) {
      builder.append("def ");
    }

    //This is for constructor creation
    if (type != null) {
      builder.append(type);
      builder.append(" ");
    }

    builder.append(name);
    builder.append("(");

    for (int i = 0; i < paramNames.length; i++) {
      String paramType = paramTypes == null ? null : paramTypes[i];

      if (i > 0) builder.append(", ");

      if (paramType != null) {
        builder.append(paramType);
        builder.append(" ");
      }
      builder.append(paramNames[i]);
    }

    builder.append(")");
