/**
 * Copyright (c) 2015-present, Jim Kynde Meyer
 * All rights reserved.
 * <p>
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package com.intellij.lang.jsgraphql.endpoint.ide.completion;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import com.intellij.lang.jsgraphql.endpoint.psi.*;
import org.jetbrains.annotations.NotNull;

import com.google.common.collect.Lists;
import com.intellij.codeInsight.completion.AddSpaceInsertHandler;
import com.intellij.codeInsight.completion.CompletionContributor;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionProvider;
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.lang.jsgraphql.v1.JSGraphQLScalars;
import com.intellij.lang.jsgraphql.endpoint.JSGraphQLEndpointFileType;
import com.intellij.lang.jsgraphql.endpoint.JSGraphQLEndpointTokenTypes;
import com.intellij.lang.jsgraphql.endpoint.JSGraphQLEndpointTokenTypesSets;
import com.intellij.lang.jsgraphql.endpoint.psi.impl.JSGraphQLEndpointImplementsInterfacesImpl;
import com.intellij.lang.jsgraphql.icons.JSGraphQLIcons;
import com.intellij.lang.jsgraphql.v1.ide.configuration.JSGraphQLConfigurationProvider;
import com.intellij.lang.jsgraphql.v1.ide.configuration.JSGraphQLSchemaEndpointAnnotation;
import com.intellij.lang.jsgraphql.v1.ide.configuration.JSGraphQLSchemaEndpointAnnotationArgument;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.search.FileTypeIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ProcessingContext;


public class JSGraphQLEndpointCompletionContributor extends CompletionContributor {

	private static final ArrayList<String> TOP_LEVEL_KEYWORDS = Lists.newArrayList(
			JSGraphQLEndpointTokenTypes.IMPORT.toString(),
			JSGraphQLEndpointTokenTypes.TYPE.toString(),
			JSGraphQLEndpointTokenTypes.INTERFACE.toString(),
			JSGraphQLEndpointTokenTypes.INPUT.toString(),
			JSGraphQLEndpointTokenTypes.UNION.toString(),
			JSGraphQLEndpointTokenTypes.ENUM.toString(),
			JSGraphQLEndpointTokenTypes.SCALAR.toString(),
			JSGraphQLEndpointTokenTypes.SCHEMA.toString(),
			JSGraphQLEndpointTokenTypes.ANNOTATION_DEF.toString()
	);

	public JSGraphQLEndpointCompletionContributor() {

		CompletionProvider<CompletionParameters> provider = new CompletionProvider<CompletionParameters>() {
			@Override
			protected void addCompletions(@NotNull final CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) {

				final PsiFile file = parameters.getOriginalFile();

				if (!(file instanceof JSGraphQLEndpointFile)) {
					return;
				}

				final boolean autoImport = parameters.isExtendedCompletion() || parameters.getCompletionType() == CompletionType.SMART;

				final PsiElement completionElement = Optional.ofNullable(parameters.getOriginalPosition()).orElse(parameters.getPosition());
				if (completionElement != null) {

					final PsiElement parent = completionElement.getParent();
					final PsiElement leafBeforeCompletion = PsiTreeUtil.prevVisibleLeaf(completionElement);

					// 1. complete on interface name after IMPLEMENTS token
					if (completeImplementableInterface(result, autoImport, completionElement, leafBeforeCompletion)) {
						return;
					}

					// 2. import file
					if (completeImportFile(result, file, parent)) {
						return;
					}

					// 3.A. top level completions, e.g. keywords and definition annotations
					if (completeKeywordsAndDefinitionAnnotations(result, autoImport, completionElement, leafBeforeCompletion, parent)) {
						return;
					}

					// 3.B. implements when type definition surrounds completion element (e.g. when it has a FieldDefinitionSet)
					if (completeImplementsInsideTypeDefinition(result, completionElement, parent)) {
						return;
					}

					// 4. completions inside FieldDefinitionSet
					final JSGraphQLEndpointFieldDefinitionSet fieldDefinitionSet = PsiTreeUtil.getParentOfType(completionElement, JSGraphQLEndpointFieldDefinitionSet.class);
					if (fieldDefinitionSet != null) {

						// 4.A. field/argument type completion
						if (completeFieldOrArgumentType(result, autoImport, completionElement)) {
							return;
						}

						// 4.B. annotations
						if (completeAnnotations(result, autoImport, file, completionElement)) {
							return;
						}

						// 4.C. annotation arguments
						if (completeAnnotationArguments(result, file, completionElement, leafBeforeCompletion)) {
							return;
						}

						// 4.D. override for interface fields
						if (completeOverrideFields(fieldDefinitionSet, completionElement, result)) {
							return;
						}
					}

					// 5. completions inside SchemaDefinition
					if (completeInsideSchemaDefinition(result, completionElement, leafBeforeCompletion)) {
						return;
					}

				}

			}
		};

		extend(CompletionType.BASIC, PlatformPatterns.psiElement(), provider);
		extend(CompletionType.SMART, PlatformPatterns.psiElement(), provider);

	}

	@Override
	public boolean invokeAutoPopup(@NotNull PsiElement position, char typeChar) {
		if(typeChar == '@') {
			// suggest annotations automatically after typing @
			return true;
		}
		return super.invokeAutoPopup(position, typeChar);
	}

	private boolean completeImplementableInterface(@NotNull CompletionResultSet result, boolean autoImport, PsiElement completionElement, PsiElement leafBeforeCompletion) {
		if (leafBeforeCompletion != null) {
			final TokenSet skipping = TokenSet.create(JSGraphQLEndpointTokenTypes.IDENTIFIER, JSGraphQLEndpointTokenTypes.COMMA);
			final PsiElement implementsBefore = findPreviousLeaf(completionElement, JSGraphQLEndpointTokenTypes.IMPLEMENTS, skipping);
			if (implementsBefore != null) {
				final Collection<JSGraphQLEndpointTypeResult<JSGraphQLEndpointInterfaceTypeDefinition>> availableInterfaceNames = getAvailableInterfaceNames(implementsBefore, autoImport);
				for (JSGraphQLEndpointTypeResult typeResult : availableInterfaceNames) {
					LookupElementBuilder element = withAutoImport(LookupElementBuilder.create(typeResult.name), typeResult, autoImport);
					result.addElement(element);
				}
				return true;
			}
		}
		return false;
	}

	private boolean completeImplementsKeyword(@NotNull CompletionResultSet result, PsiElement completionElement) {
		final PsiElement typeBefore = findPreviousLeaf(completionElement, JSGraphQLEndpointTokenTypes.TYPE, TokenSet.create(JSGraphQLEndpointTokenTypes.IDENTIFIER));
		if (typeBefore != null) {
			LookupElementBuilder element = LookupElementBuilder.create(JSGraphQLEndpointTokenTypes.IMPLEMENTS.toString())
					.withBoldness(true)
					.withInsertHandler(AddSpaceInsertHandler.INSTANCE_WITH_AUTO_POPUP);
			result.addElement(element);
			return true;
		}
		return false;
	}

	private boolean completeImportFile(@NotNull CompletionResultSet result, PsiFile file, PsiElement parent) {
		if ((parent instanceof JSGraphQLEndpointQuotedString || parent instanceof JSGraphQLEndpointString) && PsiTreeUtil.getParentOfType(parent, JSGraphQLEndpointImportFileReference.class) != null) {

			final Project project = file.getProject();
			final VirtualFile entryFile = JSGraphQLConfigurationProvider.getService(project).getEndpointEntryFile(file);
			final GlobalSearchScope scope = JSGraphQLEndpointPsiUtil.getImportScopeFromEntryFile(project, entryFile, file);
			final Collection<VirtualFile> files = FileTypeIndex.getFiles(JSGraphQLEndpointFileType.INSTANCE, scope);
			for (VirtualFile virtualFile : files) {
				if(virtualFile.equals(entryFile)) {
					// entry file should never be imported
					continue;
				}
				final PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
				if (psiFile != null) {
					if(psiFile.equals(file)) {
						// don't suggest the current file
						continue;
					}
					String name = JSGraphQLEndpointImportUtil.getImportName(project, psiFile);
					result.addElement(LookupElementBuilder.create(name).withIcon(psiFile.getIcon(0)));
				}
			}
			return true;
		}
		return false;
	}

	private boolean completeKeywordsAndDefinitionAnnotations(@NotNull CompletionResultSet result, boolean autoImport, PsiElement completionElement, PsiElement leafBeforeCompletion, PsiElement parent) {
		if (parent instanceof JSGraphQLEndpointFile || isTopLevelError(parent)) {

			if (isKeyword(leafBeforeCompletion)) {
				// no keyword suggestions right after another keyword
				return true;
			}

			// implements after TYPE NamedType
			if(completeImplementsKeyword(result, completionElement)) {
				return true;
			}

			for (String keyword : TOP_LEVEL_KEYWORDS) {
				LookupElementBuilder element = LookupElementBuilder.create(keyword).withBoldness(true);
				if(keyword.equals(JSGraphQLEndpointTokenTypes.IMPORT.toString())) {
					element = element.withInsertHandler(JSGraphQLEndpointImportInsertHandler.INSTANCE_WITH_AUTO_POPUP);
				} else {
					element = element.withInsertHandler(AddSpaceInsertHandler.INSTANCE_WITH_AUTO_POPUP);
				}
				result.addElement(element);
			}

			completeAnnotations(result, autoImport, completionElement.getContainingFile(), completionElement);

			return true;

		}
		if(parent instanceof JSGraphQLEndpointAnnotation && parent.getParent() instanceof JSGraphQLEndpointNamedTypeDefinition) {
			// completing inside a definition/top level annotation
			return completeAnnotations(result, autoImport, completionElement.getContainingFile(), completionElement);
		}
		return false;
	}

	private boolean completeImplementsInsideTypeDefinition(@NotNull CompletionResultSet result, PsiElement completionElement, PsiElement parent) {
		if(parent instanceof JSGraphQLEndpointObjectTypeDefinition) {
			// implements after TYPE NamedType
			if(completeImplementsKeyword(result, completionElement)) {
				return true;
			}
		}
		return false;
	}


	private boolean completeFieldOrArgumentType(@NotNull CompletionResultSet result, boolean autoImport, PsiElement completionElement) {
		final TokenSet skipping = TokenSet.create(JSGraphQLEndpointTokenTypes.LBRACKET);
		final PsiElement colonBefore = findPreviousLeaf(completionElement, JSGraphQLEndpointTokenTypes.COLON, skipping);
		if (colonBefore != null) {
			for (String scalarType : JSGraphQLScalars.SCALAR_TYPES) {
				LookupElementBuilder element = LookupElementBuilder.create(scalarType).withIcon(JSGraphQLIcons.Schema.Scalar);
				result.addElement(element);
			}
			for (JSGraphQLEndpointTypeResult enumType : getKnownEnumTypeNames(completionElement, autoImport)) {
				LookupElementBuilder element = LookupElementBuilder.create(enumType.name).withIcon(JSGraphQLIcons.Schema.Enum);
				element = withAutoImport(element, enumType, autoImport);
				result.addElement(element);
			}
			final boolean isInsideInputValueDefinitions = PsiTreeUtil.getParentOfType(colonBefore, JSGraphQLEndpointInputValueDefinitions.class) != null;
			final JSGraphQLEndpointInputObjectTypeDefinition inputObjectTypeDefinition = PsiTreeUtil.getParentOfType(colonBefore, JSGraphQLEndpointInputObjectTypeDefinition.class);
			final boolean isInsideInputType = inputObjectTypeDefinition != null;
			if (isInsideInputValueDefinitions || isInsideInputType) {
				// argument (input) or input type definition
				final Collection<JSGraphQLEndpointTypeResult<JSGraphQLEndpointInputObjectTypeDefinition>> knownInputTypeNames = getKnownInputTypeNames(completionElement, autoImport);
				if(isInsideInputType) {
					// input types can only refer to other input types: http://facebook.github.io/graphql/#sec-Input-Objects
					if(inputObjectTypeDefinition.getNamedTypeDef() != null) {
						knownInputTypeNames.removeIf(t -> t.name.equals(inputObjectTypeDefinition.getNamedTypeDef().getText()));
					}
				}
				for (JSGraphQLEndpointTypeResult typeResult : knownInputTypeNames) {
					LookupElementBuilder element = LookupElementBuilder.create(typeResult.name).withIcon(JSGraphQLIcons.Schema.Type);
					element = withAutoImport(element, typeResult, autoImport);
					result.addElement(element);
				}
			} else {
				// field return type (type, interface, union)
				for (JSGraphQLEndpointTypeResult typeResult : getAvailableInterfaceNames(completionElement, autoImport)) {
					LookupElementBuilder element = LookupElementBuilder.create(typeResult.name).withIcon(JSGraphQLIcons.Schema.Interface);
					element = withAutoImport(element, typeResult, autoImport);
					result.addElement(element);
				}
				for (JSGraphQLEndpointTypeResult typeResult : getKnownTypeNames(completionElement, autoImport)) {
					LookupElementBuilder element = LookupElementBuilder.create(typeResult.name).withIcon(JSGraphQLIcons.Schema.Type);
					element = withAutoImport(element, typeResult, autoImport);
					result.addElement(element);
				}
				for (JSGraphQLEndpointTypeResult typeResult : getKnownUnionTypeNames(completionElement, autoImport)) {
					LookupElementBuilder element = LookupElementBuilder.create(typeResult.name).withIcon(JSGraphQLIcons.Schema.Type);
					element = withAutoImport(element, typeResult, autoImport);
					result.addElement(element);
				}
			}
			return true;
		}
		return false;
	}

	private boolean completeAnnotations(@NotNull CompletionResultSet result, boolean autoImport, PsiFile file, PsiElement completionElement) {
		final JSGraphQLEndpointFieldDefinition field = PsiTreeUtil.getNextSiblingOfType(completionElement, JSGraphQLEndpointFieldDefinition.class);
		final JSGraphQLEndpointProperty property = PsiTreeUtil.getNextSiblingOfType(completionElement, JSGraphQLEndpointProperty.class);
		final JSGraphQLEndpointAnnotation nextAnnotation = PsiTreeUtil.getNextSiblingOfType(completionElement, JSGraphQLEndpointAnnotation.class);
		final boolean afterAtAnnotation = completionElement.getNode().getElementType() == JSGraphQLEndpointTokenTypes.AT_ANNOTATION;
		final boolean isTopLevelCompletion = completionElement.getParent() instanceof JSGraphQLEndpointFile;
		if (afterAtAnnotation || isTopLevelCompletion || field != null || nextAnnotation != null || property != null) {
			final JSGraphQLConfigurationProvider configurationProvider = JSGraphQLConfigurationProvider.getService(file.getProject());
			for (JSGraphQLSchemaEndpointAnnotation endpointAnnotation : configurationProvider.getEndpointAnnotations(file)) {
				String completion = endpointAnnotation.name;
				if (!afterAtAnnotation) {
					completion = "@" + completion;
				}
				LookupElementBuilder element = LookupElementBuilder.create(completion).withIcon(JSGraphQLIcons.Schema.Attribute);
				if(endpointAnnotation.arguments != null && endpointAnnotation.arguments.size() > 0) {
					element = element.withInsertHandler(ParenthesesInsertHandler.WITH_PARAMETERS);
				}
				result.addElement(element);
			}
			return true;
		}
		return false;
	}

	private boolean completeAnnotationArguments(@NotNull CompletionResultSet result, PsiFile file, PsiElement completionElement, PsiElement leafBeforeCompletion) {
		final JSGraphQLEndpointAnnotationArguments annotationArguments = PsiTreeUtil.getParentOfType(completionElement, JSGraphQLEndpointAnnotationArguments.class);
		if (annotationArguments != null && leafBeforeCompletion != null) {
			final JSGraphQLEndpointAnnotation annotation = PsiTreeUtil.getParentOfType(annotationArguments, JSGraphQLEndpointAnnotation.class);
			if(annotation != null) {
				final JSGraphQLConfigurationProvider configurationProvider = JSGraphQLConfigurationProvider.getService(file.getProject());
				for (JSGraphQLSchemaEndpointAnnotation endpointAnnotation : configurationProvider.getEndpointAnnotations(file)) {
					if (annotation.getAtAnnotation().getText().equals("@" + endpointAnnotation.name)) {
						final IElementType elementType = leafBeforeCompletion.getNode().getElementType();
						if(elementType == JSGraphQLEndpointTokenTypes.LPAREN || elementType == JSGraphQLEndpointTokenTypes.COMMA) {
							// completion on argument name
							if(endpointAnnotation.arguments != null) {
								for (JSGraphQLSchemaEndpointAnnotationArgument argument : endpointAnnotation.arguments) {
									LookupElementBuilder element = LookupElementBuilder.create(argument.name + " = ").withTypeText(" " + argument.type, true);
									element = element.withPresentableText(argument.name + "");
									result.addElement(element);
								}
							}
						} else if(elementType == JSGraphQLEndpointTokenTypes.EQUALS) {
							// completion on argument value
							if(endpointAnnotation.arguments != null) {
								final PsiElement annotationName = PsiTreeUtil.prevVisibleLeaf(leafBeforeCompletion);
								if(annotationName != null) {
									for (JSGraphQLSchemaEndpointAnnotationArgument argument : endpointAnnotation.arguments) {
										if(annotationName.getText().equals(argument.name)) {
											if("Boolean".equals(argument.type)) {
												result.addElement(LookupElementBuilder.create("true").withBoldness(true));
												result.addElement(LookupElementBuilder.create("false").withBoldness(true));
											}
											return true;
										}
									}
								}
							}
						}
						return true;
					}
				}
			}
			return true;
		}
		return false;
	}

	private boolean completeOverrideFields(JSGraphQLEndpointFieldDefinitionSet fieldDefinitionSet, PsiElement completionElement, CompletionResultSet result) {
		if(PsiTreeUtil.getParentOfType(completionElement, JSGraphQLEndpointAnnotation.class) != null) {
			return false;
		}
		final JSGraphQLEndpointObjectTypeDefinition typeDefinition = PsiTreeUtil.getParentOfType(fieldDefinitionSet, JSGraphQLEndpointObjectTypeDefinition.class);
		if (typeDefinition != null) {
			if (typeDefinition.getImplementsInterfaces() != null) {
				final Set<String> implementsNames = typeDefinition.getImplementsInterfaces().getNamedTypeList().stream().map(t -> t.getIdentifier().getText()).collect(Collectors.toSet());
				final Collection<JSGraphQLEndpointInterfaceTypeDefinition> interfaceTypeDefinitions = JSGraphQLEndpointPsiUtil.getKnownDefinitions(
						fieldDefinitionSet.getContainingFile(),
						JSGraphQLEndpointInterfaceTypeDefinition.class,
						false,
						null
				);
				final Set<String> currentFieldNames = fieldDefinitionSet.getFieldDefinitionList().stream().map(f -> f.getProperty().getText()).collect(Collectors.toSet());
				for (JSGraphQLEndpointInterfaceTypeDefinition interfaceTypeDefinition : interfaceTypeDefinitions) {
					if (interfaceTypeDefinition.getNamedTypeDef() != null) {
						if (implementsNames.contains(interfaceTypeDefinition.getNamedTypeDef().getText())) {
							if (interfaceTypeDefinition.getFieldDefinitionSet() != null) {
								for (JSGraphQLEndpointFieldDefinition field : interfaceTypeDefinition.getFieldDefinitionSet().getFieldDefinitionList()) {
									if (!currentFieldNames.contains(field.getProperty().getText())) {
										LookupElementBuilder element = LookupElementBuilder.create(field.getText().trim());
										element = element.withTypeText(" " + interfaceTypeDefinition.getNamedTypeDef().getText(), true);
										result.addElement(element);
									}
								}
							}
						}
					}
				}
				return true;
			}
		}
		return false;
	}

	private boolean completeInsideSchemaDefinition(@NotNull CompletionResultSet result, PsiElement completionElement, PsiElement leafBeforeCompletion) {
		final JSGraphQLEndpointOperationTypeDefinitionSet operationSet = PsiTreeUtil.getParentOfType(completionElement, JSGraphQLEndpointOperationTypeDefinitionSet.class);
		if (operationSet != null) {
			final TokenSet skipping = TokenSet.create(JSGraphQLEndpointTokenTypes.LBRACKET);
			final PsiElement colonBefore = findPreviousLeaf(completionElement, JSGraphQLEndpointTokenTypes.COLON, skipping);
			if (colonBefore != null) {
				for (JSGraphQLEndpointTypeResult typeResult : getKnownTypeNames(completionElement, false)) {
					LookupElementBuilder element = LookupElementBuilder.create(typeResult.name).withIcon(JSGraphQLIcons.Schema.Type);
					result.addElement(element);
				}
			} else {
				if (isKeyword(leafBeforeCompletion)) {
					return true;
				}
				for (IElementType operationType : Lists.newArrayList(JSGraphQLEndpointTokenTypes.QUERY, JSGraphQLEndpointTokenTypes.MUTATION, JSGraphQLEndpointTokenTypes.SUBSCRIPTION)) {
					final String completion = operationType.toString();
					LookupElementBuilder element = LookupElementBuilder.create(completion + ":")
							.withPresentableText(completion)
							.withBoldness(true)
							.withInsertHandler(AddSpaceInsertHandler.INSTANCE_WITH_AUTO_POPUP);
					result.addElement(element);
				}
			}
			return true;
		}
		return false;
	}

	private LookupElementBuilder withAutoImport(LookupElementBuilder element, JSGraphQLEndpointTypeResult typeResult, boolean autoImport) {
		if(autoImport && typeResult.fileToImport != null) {
			element = element.withInsertHandler(new JSGraphQLEndpointAutoImportInsertHandler(typeResult.fileToImport));
			element = element.withTypeText(typeResult.fileToImport.getName(), true);
		}
		return element;
	}


	// ---- PSI file traversals ----

	private boolean isKeyword(PsiElement leafBeforeCompletion) {
		if (leafBeforeCompletion != null) {
			if (JSGraphQLEndpointTokenTypesSets.KEYWORDS.contains(leafBeforeCompletion.getNode().getElementType())) {
				return true;
			}
		}
		return false;
	}

	private Collection<JSGraphQLEndpointTypeResult<JSGraphQLEndpointInterfaceTypeDefinition>> getAvailableInterfaceNames(PsiElement implementsToken, boolean autoImport) {
		final Collection<JSGraphQLEndpointTypeResult<JSGraphQLEndpointInterfaceTypeDefinition>> available = getKnownInterfaceNames(implementsToken, autoImport);
		final JSGraphQLEndpointImplementsInterfacesImpl implementsInterfaces = PsiTreeUtil.getParentOfType(implementsToken, JSGraphQLEndpointImplementsInterfacesImpl.class);
		if (implementsInterfaces != null) {
			final List<String> currentInterfaces = implementsInterfaces.getNamedTypeList().stream().map(type -> type.getIdentifier().getText()).collect(Collectors.toList());
			available.removeIf(t -> currentInterfaces.contains(t.name));
		}
		return available;
	}

	private Collection<JSGraphQLEndpointTypeResult<JSGraphQLEndpointInterfaceTypeDefinition>> getKnownInterfaceNames(PsiElement element, boolean autoImport) {
		return JSGraphQLEndpointPsiUtil.getKnownDefinitionNames(element.getContainingFile(), JSGraphQLEndpointInterfaceTypeDefinition.class, autoImport);
	}

	private Collection<JSGraphQLEndpointTypeResult<JSGraphQLEndpointObjectTypeDefinition>> getKnownTypeNames(PsiElement element, boolean autoImport) {
		return JSGraphQLEndpointPsiUtil.getKnownDefinitionNames(element.getContainingFile(), JSGraphQLEndpointObjectTypeDefinition.class, autoImport);
	}

	private Collection<JSGraphQLEndpointTypeResult<JSGraphQLEndpointInputObjectTypeDefinition>> getKnownInputTypeNames(PsiElement element, boolean autoImport) {
		return JSGraphQLEndpointPsiUtil.getKnownDefinitionNames(element.getContainingFile(), JSGraphQLEndpointInputObjectTypeDefinition.class, autoImport);
	}

	private Collection<JSGraphQLEndpointTypeResult<JSGraphQLEndpointEnumTypeDefinition>> getKnownEnumTypeNames(PsiElement element, boolean autoImport) {
		return JSGraphQLEndpointPsiUtil.getKnownDefinitionNames(element.getContainingFile(), JSGraphQLEndpointEnumTypeDefinition.class, autoImport);
	}


	private Collection<JSGraphQLEndpointTypeResult<JSGraphQLEndpointUnionTypeDefinition>> getKnownUnionTypeNames(PsiElement element, boolean autoImport) {
		return JSGraphQLEndpointPsiUtil.getKnownDefinitionNames(element.getContainingFile(), JSGraphQLEndpointUnionTypeDefinition.class, autoImport);
	}

	private Collection<JSGraphQLEndpointTypeResult<JSGraphQLEndpointAnnotationDefinition>> getKnownAnnotationNames(PsiElement element, boolean autoImport) {
		return JSGraphQLEndpointPsiUtil.getKnownDefinitionNames(element.getContainingFile(), JSGraphQLEndpointAnnotationDefinition.class, autoImport);
	}

	private PsiElement findPreviousLeaf(PsiElement start, IElementType typeToFind, TokenSet skipping) {
		PsiElement result = PsiTreeUtil.prevVisibleLeaf(start);
		while (result != null) {
			if (result instanceof PsiWhiteSpace || result instanceof PsiComment) {
				result = PsiTreeUtil.prevVisibleLeaf(result);
				continue;
			}
			if (result.getNode().getElementType() == typeToFind) {
				return result;
			}
			if (!skipping.contains(result.getNode().getElementType())) {
				// not allowed
				return null;
			}
			result = PsiTreeUtil.prevVisibleLeaf(result);
		}
		return null;
	}

	private boolean isTopLevelError(PsiElement element) {
		return (element instanceof PsiErrorElement) && (element.getParent() instanceof PsiFile);
	}


}