// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.completion.modcommand;

import com.intellij.application.options.CodeStyle;
import com.intellij.codeInsight.BlockUtils;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.JavaTailTypes;
import com.intellij.codeInsight.ModNavigatorTailType;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.daemon.impl.quickfix.CreateClassKind;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.java.codeserver.core.JavaPsiSwitchUtil;
import com.intellij.java.syntax.parser.JavaKeywords;
import com.intellij.modcompletion.ModCompletionItem;
import com.intellij.modcompletion.ModCompletionItemPresentation;
import com.intellij.modcompletion.ModCompletionItemProvider;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.Conditions;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.MarkupText;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PsiElementPattern;
import com.intellij.patterns.PsiJavaPatterns;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.templateLanguages.OuterLanguageElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.*;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.SealedUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static com.intellij.codeInsight.ModNavigatorTailType.*;
import static com.intellij.openapi.util.Conditions.notInstanceOf;
import static com.intellij.openapi.util.text.MarkupText.Kind.*;
import static com.intellij.patterns.PlatformPatterns.psiElement;
import static com.intellij.patterns.PsiJavaPatterns.*;
import static com.intellij.patterns.StandardPatterns.not;
import static com.intellij.psi.SyntaxTraverser.psiApi;

/**
 * A provider for Java keywords completion.
 * <p> 
 * TODO: disable completion chars
 */
@NotNullByDefault
final class KeywordCompletionItemProvider implements ModCompletionItemProvider {
  private static final ElementPattern<PsiElement> AFTER_DOT = psiElement().afterLeaf(".");

  private static final ElementPattern<PsiElement> VARIABLE_AFTER_FINAL =
    psiElement().afterLeaf(JavaKeywords.FINAL).inside(PsiDeclarationStatement.class);

  private static final ElementPattern<PsiElement> INSIDE_PARAMETER_LIST =
    psiElement().withParent(
      psiElement(PsiJavaCodeReferenceElement.class).insideStarting(
        psiElement().withTreeParent(
          psiElement(PsiParameterList.class).andNot(psiElement(PsiAnnotationParameterList.class)))));
  private static final ElementPattern<PsiElement> INSIDE_RECORD_HEADER =
    psiElement().withParent(
      psiElement(PsiJavaCodeReferenceElement.class).insideStarting(
        or(
          psiElement().withTreeParent(
            psiElement(PsiRecordComponent.class)),
          psiElement().withTreeParent(
            psiElement(PsiRecordHeader.class)
          )
        )
      ));
  private static final ElementPattern<PsiElement> AFTER_NEW = psiElement().afterLeaf(psiElement().withText(JavaKeywords.NEW));
  private static final ElementPattern<PsiElement> START_SWITCH =
    psiElement().afterLeaf(psiElement().withText("{").withParents(PsiCodeBlock.class, PsiSwitchBlock.class));

  private static final ElementPattern<PsiElement> SUPER_OR_THIS_PATTERN =
    and(JavaSmartCompletionContributor.INSIDE_EXPRESSION,
        not(psiElement().afterLeaf(JavaKeywords.CASE)),
        not(psiElement().afterLeaf(psiElement().withText(".").afterLeaf(JavaKeywords.THIS, JavaKeywords.SUPER))),
        not(psiElement().inside(PsiAnnotation.class)),
        not(START_SWITCH),
        not(JavaMemberNameCompletionContributor.INSIDE_TYPE_PARAMS_PATTERN));

  private static final PsiElementPattern<PsiElement, ?> START_FOR = psiElement().afterLeaf(psiElement().withText("(").afterLeaf("for"));

  @Override
  public void provideItems(CompletionContext context, Consumer<ModCompletionItem> sink) {
    new KeywordAdder(context, sink).provideItems();
  }

  private static class KeywordAdder {
    private final CompletionContext myContext;
    private final Consumer<ModCompletionItem> mySink;
    private final PsiElement myPosition;
    private final @Nullable PsiElement myPrevLeaf;
    private final PsiFile myFile;
    private final PrefixMatcher myKeywordMatcher;

    KeywordAdder(CompletionContext context, Consumer<ModCompletionItem> sink) {
      myContext = context;
      mySink = sink;
      myPosition = context.element();
      myFile = context.getOriginalFile();
      myPrevLeaf = PsiTreeUtil.prevCodeLeaf(myPosition);
      myKeywordMatcher = new StartOnlyMatcher(context.matcher());
    }

    void provideItems() {
      if (!myContext.isSmart()) {
        addKeywords();
      }
      addEnumCases();
      addEnhancedCases();
    }

    private void addKeywords() {
      if (!canAddKeywords()) return;
      if (processAnnotationAttributes()) return;
      if (PsiJavaModule.MODULE_INFO_FILE.equals(myFile.getName()) && PsiUtil.isAvailable(JavaFeature.MODULES, myFile)) {
        addModuleKeywords();
        return;
      }

      if (addWildcardExtendsSuper()) {
        return;
      }
      addFinal();
      addWhen();
      boolean statementPosition = isStatementPosition(myPosition);
      if (statementPosition) {

        if (START_SWITCH.accepts(myPosition)) {
          return;
        }

        addBreakContinue();
        addStatementKeywords();

        if (myPrevLeaf != null &&
            myPrevLeaf.textMatches("}") &&
            myPrevLeaf.getParent() instanceof PsiCodeBlock &&
            myPrevLeaf.getParent().getParent() instanceof PsiTryStatement &&
            myPrevLeaf.getParent().getNextSibling() instanceof PsiErrorElement) {
          return;
        }
      }
      else {
        PsiSwitchLabeledRuleStatement rule = findEnclosingSwitchRule(myPosition);
        if (rule != null) {
          addSwitchRuleKeywords(rule);
        }
      }

      addThisSuper();
      addExpressionKeywords(statementPosition);
      addFileHeaderKeywords();
      addInstanceof();
      addClassKeywords();
      addMethodHeaderKeywords();
      addPrimitiveTypes();
      addVar();
      addClassLiteral();
      addExtendsImplements();
      addCaseNullToSwitch();
    }

    private boolean processAnnotationAttributes() {
      PsiAnnotation annotation = JavaCompletionContributor.findAnnotationWhoseAttributeIsCompleted(myPosition);
      if (annotation != null) {
        if (myPosition instanceof PsiIdentifier) {
            PsiClass annoClass = annotation.resolveAnnotationType();
            if (annoClass != null) {
              addPrimitiveTypes();
            }
        }
        return true;
      }
      return false;
    }

    private void addCaseNullToSwitch() {
      if (!isInsideCaseLabel()) return;

      final PsiSwitchBlock switchBlock = PsiTreeUtil.getParentOfType(myPosition, PsiSwitchBlock.class, false, PsiMember.class);
      if (switchBlock == null) return;

      final PsiType selectorType = getSelectorType(switchBlock);
      if (selectorType instanceof PsiPrimitiveType) return;

      addKeyword(createKeyword(JavaKeywords.NULL));
    }

    private void addKeyword(ModCompletionItem item) {
      if (myKeywordMatcher.prefixMatches(item.mainLookupString())) {
        mySink.accept(item);
      }
    }

    private boolean isInsideCaseLabel() {
      if (!PsiUtil.isAvailable(JavaFeature.PATTERNS_IN_SWITCH, myPosition)) return false;
      return PsiJavaPatterns.psiElement().withSuperParent(2, PsiCaseLabelElementList.class).accepts(myPosition);
    }
    
    private void addExtendsImplements() {
      if (myPrevLeaf == null ||
          !(myPrevLeaf instanceof PsiIdentifier || myPrevLeaf.textMatches(">") || myPrevLeaf.textMatches(")"))) {
        return;
      }

      PsiClass psiClass = null;
      PsiElement prevParent = myPrevLeaf.getParent();
      if (myPrevLeaf instanceof PsiIdentifier && prevParent instanceof PsiClass) {
        psiClass = (PsiClass)prevParent;
      }
      else {
        PsiReferenceList referenceList = PsiTreeUtil.getParentOfType(myPrevLeaf, PsiReferenceList.class);
        if (referenceList != null && referenceList.getParent() instanceof PsiClass) {
          psiClass = (PsiClass)referenceList.getParent();
        }
        else if ((prevParent instanceof PsiTypeParameterList || prevParent instanceof PsiRecordHeader)
                 && prevParent.getParent() instanceof PsiClass) {
          psiClass = (PsiClass)prevParent.getParent();
        }
      }

      if (psiClass != null) {
        if (!psiClass.isEnum() && !psiClass.isRecord()) {
          addKeyword(createKeyword(JavaKeywords.EXTENDS, humbleSpaceBeforeWordType()));
          if (PsiUtil.isAvailable(JavaFeature.SEALED_CLASSES, psiClass)) {
            PsiModifierList modifiers = psiClass.getModifierList();
            if (myContext.invocationCount() > 1 ||
                (modifiers != null &&
                 !modifiers.hasExplicitModifier(PsiModifier.FINAL) &&
                 !modifiers.hasExplicitModifier(PsiModifier.NON_SEALED))) {
              CommonCompletionItem permits =
                createKeyword(JavaKeywords.PERMITS, humbleSpaceBeforeWordType());
              if (modifiers != null && !modifiers.hasExplicitModifier(PsiModifier.SEALED)) {
                permits = permits
                  .withAdditionalUpdater((start, updater) -> {
                    PsiClass aClass = PsiTreeUtil.findElementOfClassAtOffset(updater.getPsiFile(), start, PsiClass.class, false);
                    if (aClass != null) {
                      PsiModifierList modifierList = aClass.getModifierList();
                      if (modifierList != null) {
                        modifierList.setModifierProperty(PsiModifier.SEALED, true);
                      }
                    }
                  });
              }
              addKeyword(permits);
            }
          }
        }
        if (!psiClass.isInterface() && !(psiClass instanceof PsiTypeParameter)) {
          addKeyword(createKeyword(JavaKeywords.IMPLEMENTS, humbleSpaceBeforeWordType()));
        }
      }
    }
    
    private void addClassLiteral() {
      if (JavaKeywordCompletion.isAfterTypeDot(myPosition)) {
        addKeyword(createKeyword(JavaKeywords.CLASS));
      }
    }

    private void addVar() {
      if (isVarAllowed()) {
        addKeyword(createKeyword(JavaKeywords.VAR, humbleSpaceBeforeWordType()));
      }
    }

    private boolean isVarAllowed() {
      if (PsiUtil.isAvailable(JavaFeature.VAR_LAMBDA_PARAMETER, myPosition) && isLambdaParameterType(myPosition)) {
        return true;
      }

      if (!PsiUtil.isAvailable(JavaFeature.LVTI, myPosition)) return false;

      if (isAtCatchOrResourceVariableStart(myPosition) && PsiTreeUtil.getParentOfType(myPosition, PsiCatchSection.class) == null) {
        return true;
      }

      return isVariableTypePosition(myPosition) &&
             PsiTreeUtil.getParentOfType(myPosition, PsiCodeBlock.class, true, PsiMember.class, PsiLambdaExpression.class) != null;
    }

    private void addPrimitiveTypes() {
      if (AFTER_DOT.accepts(myPosition) ||
          psiElement().inside(psiAnnotation()).accepts(myPosition) && !expectsClassLiteral(myPosition)) {
        return;
      }

      if (JavaPatternCompletionUtil.insideDeconstructionList(myPosition)) {
        suggestPrimitivesInsideDeconstructionListPattern();
        return;
      }

      if (JavaKeywordCompletion.afterInstanceofForType(myPosition)) {
        PsiInstanceOfExpression instanceOfExpression = PsiTreeUtil.getParentOfType(myPosition, PsiInstanceOfExpression.class);
        if (instanceOfExpression != null) {
          suggestPrimitiveTypesForPattern(instanceOfExpression.getOperand().getType());
        }
        return;
      }

      if (JavaKeywordCompletion.afterCaseForType(myPosition)) {
        PsiSwitchBlock switchBlock = PsiTreeUtil.getParentOfType(myPosition, PsiSwitchBlock.class);
        if (switchBlock != null && switchBlock.getExpression() != null) {
          suggestPrimitiveTypesForPattern(switchBlock.getExpression().getType());
        }
        if (switchBlock != null && switchBlock.getExpression() != null) {
          PsiType type = switchBlock.getExpression().getType();
          if (PsiTypes.booleanType().equals(PsiPrimitiveType.getOptionallyUnboxedType(type))) {
            Set<String> branches = JavaPsiSwitchUtil.getSwitchBranches(switchBlock).stream()
              .map(branch -> branch instanceof PsiExpression expression ? ExpressionUtils.computeConstantExpression(expression) : null)
              .filter(constant -> constant instanceof Boolean)
              .map(branch -> branch.toString())
              .collect(Collectors.toSet());
            ModNavigatorTailType tailType = JavaTailTypes.forSwitchLabel(switchBlock);
            for (String keyword : List.of(JavaKeywords.TRUE, JavaKeywords.FALSE)) {
              if(branches.contains(keyword)) continue;
              addKeyword(createKeyword(keyword, tailType));
            }
          }
        }
        return;
      }

      boolean afterNew = AFTER_NEW.accepts(myPosition) &&
                         !psiElement().afterLeaf(psiElement().afterLeaf(".")).accepts(myPosition);
      if (afterNew) {
        Set<PsiType> expected = ContainerUtil.map2Set(JavaSmartCompletionContributor.getExpectedTypes(myPosition, false),
                                                      ExpectedTypeInfo::getDefaultType);
        boolean addAll = expected.isEmpty() || ContainerUtil.exists(expected, t ->
          t.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) || t.equalsToText(CommonClassNames.JAVA_IO_SERIALIZABLE));
        for (String primitiveType : PsiTypes.primitiveTypeNames()) {
          PsiType array = Objects.requireNonNull(PsiTypes.primitiveTypeByName(primitiveType)).createArrayType();
          if (addAll || expected.contains(array)) {
            addKeyword(PsiTypeCompletionItem.create(array));
          }
        }
        return;
      }

      boolean inCast = psiElement()
        .afterLeaf(psiElement().withText("(").withParent(PsiJavaPatterns.psiElement(PsiParenthesizedExpression.class, PsiTypeCastExpression.class)))
        .accepts(myPosition);

      boolean typeFragment = myPosition.getContainingFile() instanceof PsiTypeCodeFragment && PsiTreeUtil.prevVisibleLeaf(myPosition) == null;
      boolean declaration = isDeclarationStart(myPosition);
      boolean expressionPosition = isExpressionPosition(myPosition);
      boolean inGenerics = PsiTreeUtil.getParentOfType(myPosition, PsiReferenceParameterList.class) != null;
      if ((isVariableTypePosition(myPosition) ||
           inGenerics ||
           inCast ||
           declaration ||
           typeFragment ||
           expressionPosition) && primitivesAreExpected(myPosition)) {
        for (String primitiveType : PsiTypes.primitiveTypeNames()) {
          addKeyword(createKeyword(primitiveType).withObject(Objects.requireNonNull(PsiTypes.primitiveTypeByName(primitiveType))));
        }
        if (expressionPosition) {
          addKeyword(createKeyword(JavaKeywords.VOID).withObject(PsiTypes.voidType()));
        }
      }
      if (declaration) {
        addKeyword(
          createKeyword(JavaKeywords.VOID, humbleSpaceBeforeWordType()).withObject(PsiTypes.voidType()));
      }
      else if (typeFragment && ((PsiTypeCodeFragment)myPosition.getContainingFile()).isVoidValid()) {
        addKeyword(createKeyword(JavaKeywords.VOID).withObject(PsiTypes.voidType()));
      }
    }

    private void addMethodHeaderKeywords() {
      if (psiElement().withText(")").withParents(PsiParameterList.class, PsiMethod.class).accepts(myPrevLeaf)) {
        assert myPrevLeaf != null;
        if (myPrevLeaf.getParent().getParent() instanceof PsiAnnotationMethod) {
          addKeyword(createKeyword(JavaKeywords.DEFAULT, humbleSpaceBeforeWordType()));
        }
        else {
          addKeyword(createKeyword(JavaKeywords.THROWS, humbleSpaceBeforeWordType()));
        }
      }
    }

    void suggestPrimitivesInsideDeconstructionListPattern() {
      PsiRecordComponent component = JavaPatternCompletionUtil.getRecordComponentForDeconstructionComponent(myPosition);
      if (component == null) return;
      PsiType type = component.getType();
      if (PsiUtil.isAvailable(JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS, myPosition)) {
        suggestPrimitiveTypesForPattern(type);
        return;
      }
      if (type instanceof PsiPrimitiveType) {
        addKeyword(createKeyword(type.getPresentableText(), spaceType()).withObject(type));
      }
    }

    void suggestPrimitiveTypesForPattern(@Nullable PsiType fromType) {
      if (fromType == null) return;
      if (!PsiUtil.isAvailable(JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS, myPosition)) return;
      for (PsiType primitiveType : PsiTypes.primitiveTypes()) {
        if (TypeConversionUtil.areTypesConvertible(fromType, primitiveType)) {
          addKeyword(createKeyword(primitiveType.getPresentableText(), spaceType())
                          .withObject(primitiveType));
        }
      }
    }

    private void addClassKeywords() {
      if (isSuitableForClass(myPosition)) {
        for (String s : ModifierChooser.getKeywords(myPosition)) {
          addKeyword(createKeyword(s, humbleSpaceBeforeWordType()));
        }

        if (psiElement().insideStarting(PsiJavaPatterns.psiElement(PsiLocalVariable.class, PsiExpressionStatement.class)).accepts(myPosition)) {
          addKeyword(createKeyword(JavaKeywords.CLASS, humbleSpaceBeforeWordType()));
          @NlsSafe String abstractClass = "abstract class";
          addKeyword(new CommonCompletionItem(abstractClass)
                          .withPresentation(MarkupText.plainText(abstractClass).highlightAll(MarkupText.Kind.STRONG))
                          .withTail(humbleSpaceBeforeWordType()));
          if (PsiUtil.isAvailable(JavaFeature.RECORDS, myPosition)) {
            addKeyword(createKeyword(JavaKeywords.RECORD, humbleSpaceBeforeWordType()));
          }
          if (PsiUtil.isAvailable(JavaFeature.LOCAL_ENUMS, myPosition)) {
            addKeyword(createKeyword(JavaKeywords.ENUM, humbleSpaceBeforeWordType()));
          }
          if (PsiUtil.isAvailable(JavaFeature.LOCAL_INTERFACES, myPosition)) {
            addKeyword(createKeyword(JavaKeywords.INTERFACE, humbleSpaceBeforeWordType()));
          }
        }
        if (PsiTreeUtil.getParentOfType(myPosition, PsiExpression.class, true, PsiMember.class) == null &&
            PsiTreeUtil.getParentOfType(myPosition, PsiCodeBlock.class, true, PsiMember.class) == null) {
          List<String> keywords = new ArrayList<>();
          keywords.add(JavaKeywords.CLASS);
          keywords.add(JavaKeywords.INTERFACE);
          if (PsiUtil.isAvailable(JavaFeature.RECORDS, myPosition)) {
            keywords.add(JavaKeywords.RECORD);
          }
          if (PsiUtil.isAvailable(JavaFeature.ENUMS, myPosition)) {
            keywords.add(JavaKeywords.ENUM);
          }
          String className = recommendClassName();
          for (String keyword : keywords) {
            if (className == null) {
              addKeyword(createKeyword(keyword, humbleSpaceBeforeWordType()));
            }
            else {
              addKeyword(createTypeDeclaration(keyword, className));
            }
          }
        }
      }

      if (psiElement().withText("@").andNot(psiElement().inside(PsiParameterList.class)).andNot(psiElement().inside(psiNameValuePair()))
        .accepts(myPrevLeaf)) {
        addKeyword(createKeyword(JavaKeywords.INTERFACE, humbleSpaceBeforeWordType()));
      }
    }

    private CommonCompletionItem createTypeDeclaration(@NlsSafe String keyword, @NlsSafe String className) {
      PsiElement nextElement = PsiTreeUtil.skipWhitespacesAndCommentsForward(PsiTreeUtil.nextLeaf(myPosition));
      IElementType nextToken;
      if (nextElement instanceof PsiJavaToken token) {
        nextToken = token.getTokenType();
      }
      else {
        if (nextElement instanceof PsiParameterList l && l.getFirstChild() instanceof PsiJavaToken t) {
          nextToken = t.getTokenType();
        }
        else if (nextElement instanceof PsiCodeBlock b && b.getFirstChild() instanceof PsiJavaToken t) {
          nextToken = t.getTokenType();
        }
        else {
          nextToken = null;
        }
      }
      return new CommonCompletionItem(keyword + " " + className)
        .withPresentation(
          new ModCompletionItemPresentation(MarkupText.builder().append(keyword, STRONG).append(" " + className, NORMAL).build())
            .withMainIcon(() -> CreateClassKind.valueOf(keyword.toUpperCase(Locale.ROOT)).getKindIcon()))
        .withAdditionalUpdater((start, updater) -> {
          Document document = updater.getDocument();
          int offset = updater.getCaretOffset();
          String suffix = " ";
          if (keyword.equals(JavaKeywords.RECORD)) {
            if (JavaTokenType.LPARENTH.equals(nextToken)) {
              suffix = "";
            }
            else if (JavaTokenType.LBRACE.equals(nextToken)) {
              suffix = "() ";
            }
            else {
              suffix = "() {\n}";
            }
          }
          else if (!JavaTokenType.LBRACE.equals(nextToken)) {
            suffix = " {\n}";
          }
          if (offset < document.getTextLength() && document.getCharsSequence().charAt(offset) == ' ') {
            suffix = suffix.trim();
          }
          document.insertString(offset, suffix);
          updater.moveCaretTo(offset + 1);
        });
    }

    private @Nullable String recommendClassName() {
      if (myPrevLeaf == null) return null;
      if (!myPrevLeaf.textMatches(JavaKeywords.PUBLIC) || !(myPrevLeaf.getParent() instanceof PsiModifierList)) return null;

      if (nextIsIdentifier(myPosition)) return null;

      PsiJavaFile file = getFileForDeclaration(myPrevLeaf);
      if (file == null) return null;
      String name = file.getName();
      if (!StringUtil.endsWithIgnoreCase(name, JavaFileType.DOT_DEFAULT_EXTENSION)) return null;
      String candidate = name.substring(0, name.length() - JavaFileType.DOT_DEFAULT_EXTENSION.length());
      if (StringUtil.isJavaIdentifier(candidate)
          && !ContainerUtil.exists(file.getClasses(), c -> !(c instanceof PsiImplicitClass) && candidate.equals(c.getName()))) {
        return candidate;
      }
      return null;
    }

    private static boolean nextIsIdentifier(PsiElement position) {
      PsiElement nextLeaf = PsiTreeUtil.nextLeaf(position);
      if (nextLeaf == null) return false;
      PsiElement parent = nextLeaf.getParent();
      if (!(parent instanceof PsiJavaCodeReferenceElement)) return false;
      PsiElement grandParent = parent.getParent();
      if (!(grandParent instanceof PsiTypeElement)) return false;
      return PsiTreeUtil.skipWhitespacesAndCommentsForward(grandParent) instanceof PsiIdentifier;
    }

    private void addInstanceof() {
      if (JavaKeywordCompletion.isInstanceofPlace(myPosition)) {
        addKeyword(createKeyword(JavaKeywords.INSTANCEOF).withAdditionalUpdater((startOffset, updater, insertionContext) -> {
          Document document = updater.getDocument();
          int offset = updater.getCaretOffset();
          ModNavigatorTailType tailType = humbleSpaceBeforeWordType();
          tailType.processTail(updater, offset);
          PsiDocumentManager.getInstance(updater.getProject()).commitDocument(document);
          PsiFile file = updater.getPsiFile();
          PsiInstanceOfExpression expr = 
            PsiTreeUtil.findElementOfClassAtOffset(file, startOffset, PsiInstanceOfExpression.class, false);
          if (expr != null) {
            PsiExpression operand = expr.getOperand();
            if (operand instanceof PsiPrefixExpression prefixExpression &&
                prefixExpression.getOperationTokenType().equals(JavaTokenType.EXCL)) {
              PsiExpression negated = prefixExpression.getOperand();
              if (negated != null) {
                String space = CodeStyle.getLanguageSettings(file).SPACE_WITHIN_PARENTHESES ? " " : "";
                int startParens = negated.getTextRange().getStartOffset();
                document.insertString(startParens, "(" + space);
                int endParens = updater.getCaretOffset();
                document.insertString(endParens, space + ")");
                updater.registerTabOut(TextRange.create(startParens + 1, endParens + space.length()), endParens + space.length() + 1);
              }
            }
            else if ('!' == insertionContext.insertionCharacter()) {
              // TODO: suppress ! typing
              String space = CodeStyle.getLanguageSettings(file).SPACE_WITHIN_PARENTHESES ? " " : "";
              document.insertString(expr.getTextRange().getStartOffset(), "!(" + space);
              document.insertString(updater.getCaretOffset(), space + ")");
            }
          }
        }));
      }
    }

    private void addFileHeaderKeywords() {
      PsiFile file = myPosition.getContainingFile();
      assert file != null;

      if (!(file instanceof PsiExpressionCodeFragment) &&
          !(file instanceof PsiJavaCodeReferenceCodeFragment) &&
          !(file instanceof PsiTypeCodeFragment)) {
        PsiMember parentMember = PsiTreeUtil.getParentOfType(myPosition, PsiMember.class);
        boolean bogusDeclarationInImplicitClass =
          parentMember instanceof PsiField field &&
          field.getParent() instanceof PsiImplicitClass implicitClass &&
          StreamEx.of(implicitClass.getChildren()).select(PsiMember.class).findFirst().orElse(null) == field;
        if (myPrevLeaf == null ||
            bogusDeclarationInImplicitClass && file instanceof PsiJavaFile javaFile && javaFile.getPackageStatement() == null &&
            javaFile.getImportList() != null && javaFile.getImportList().getAllImportStatements().length == 0) {
          addKeyword(createKeyword(JavaKeywords.PACKAGE, humbleSpaceBeforeWordType()));
          addKeyword(createKeyword(JavaKeywords.IMPORT, humbleSpaceBeforeWordType()));
        }
        else if (psiElement().inside(psiAnnotation().withParents(PsiModifierList.class, PsiFile.class)).accepts(myPrevLeaf)
                 && PsiPackage.PACKAGE_INFO_FILE.equals(file.getName())) {
          addKeyword(createKeyword(JavaKeywords.PACKAGE, humbleSpaceBeforeWordType()));
        }
        else if (isEndOfBlock(myPosition) && (parentMember == null || bogusDeclarationInImplicitClass)) {
          addKeyword(createKeyword(JavaKeywords.IMPORT, humbleSpaceBeforeWordType()));
        }
      }

      if (PsiUtil.isAvailable(JavaFeature.STATIC_IMPORTS, file) && myPrevLeaf != null && myPrevLeaf.textMatches(JavaKeywords.IMPORT)) {
        addKeyword(createKeyword(JavaKeywords.STATIC, humbleSpaceBeforeWordType()));
      }

      if (PsiUtil.isAvailable(JavaFeature.MODULE_IMPORT_DECLARATIONS, file) &&
          myPrevLeaf != null &&
          myPrevLeaf.textMatches(JavaKeywords.IMPORT)) {
        addKeyword(createKeyword(JavaKeywords.MODULE, humbleSpaceBeforeWordType()));
      }
    }

    private void addExpressionKeywords(boolean statementPosition) {
      if (isExpressionPosition(myPosition)) {
        PsiElement parent = myPosition.getParent();
        PsiElement grandParent = parent == null ? null : parent.getParent();
        boolean allowExprKeywords = !(grandParent instanceof PsiExpressionStatement) && !(grandParent instanceof PsiUnaryExpression);
        if (PsiTreeUtil.getParentOfType(myPosition, PsiAnnotation.class) == null) {
          if (!statementPosition) {
            addKeyword(createKeyword(JavaKeywords.NEW, insertSpaceType()));
            if (PsiUtil.isAvailable(JavaFeature.ENHANCED_SWITCH, myPosition)) {
              addKeyword(createKeyword(JavaKeywords.SWITCH, JavaTailTypes.SWITCH_LPARENTH));
            }
          }
          if (allowExprKeywords) {
            addKeyword(createKeyword(JavaKeywords.NULL));
          }
        }
        if (allowExprKeywords && mayExpectBoolean()) {
          addKeyword(createKeyword(JavaKeywords.TRUE));
          addKeyword(createKeyword(JavaKeywords.FALSE));
        }
      }

      if (isQualifiedNewContext()) {
        addKeyword(createKeyword(JavaKeywords.NEW));
      }
    }

    private void addThisSuper() {
      if (SUPER_OR_THIS_PATTERN.accepts(myPosition)) {
        boolean afterDot = AFTER_DOT.accepts(myPosition);
        boolean insideQualifierClass = isInsideQualifierClass();
        boolean insideInheritorClass = PsiUtil.isAvailable(JavaFeature.EXTENSION_METHODS, myPosition) && isInsideInheritorClass();
        if (!afterDot || insideQualifierClass || insideInheritorClass) {
          if (!afterDot || insideQualifierClass) {
            addKeyword(createKeyword(JavaKeywords.THIS));
          }

          CommonCompletionItem superItem = createKeyword(JavaKeywords.SUPER);
          if (psiElement().afterLeaf(psiElement().withText("{").withSuperParent(2, psiMethod().constructor(true))).accepts(myPosition)) {
            PsiMethod method = PsiTreeUtil.getParentOfType(myPosition, PsiMethod.class, false, PsiClass.class);
            assert method != null;
            boolean hasParams = superConstructorHasParameters(method);
            addKeyword(superItem.withAdditionalUpdater((startOffset, updater) -> {
              int offset = updater.getCaretOffset();
              Document document = updater.getDocument();
              // TODO: support '(' insertion character
              document.insertString(offset, "();");
              if (hasParams) {
                updater.moveCaretTo(offset + 1);
                updater.registerTabOut(TextRange.create(offset + 1, offset + 1), offset + 3);
              }
              else {
                updater.moveCaretTo(offset + 3);
              }
              PsiDocumentManager.getInstance(updater.getProject()).commitDocument(document);
              CodeStyleManager.getInstance(updater.getProject()).reformatText(updater.getPsiFile(), offset, offset + 3);
            }));
            return;
          }

          addKeyword(superItem);
        }
      }
    }

    private boolean isInsideInheritorClass() {
      if (myPosition.getParent() instanceof PsiJavaCodeReferenceElement) {
        final PsiElement qualifier = ((PsiJavaCodeReferenceElement)myPosition.getParent()).getQualifier();
        if (qualifier instanceof PsiJavaCodeReferenceElement) {
          final PsiElement qualifierClass = ((PsiJavaCodeReferenceElement)qualifier).resolve();
          if (qualifierClass instanceof PsiClass && ((PsiClass)qualifierClass).isInterface()) {
            PsiElement parent = myPosition;
            while ((parent = PsiTreeUtil.getParentOfType(parent, PsiClass.class, true)) != null) {
              if (PsiUtil.getEnclosingStaticElement(myPosition, (PsiClass)parent) == null &&
                  ((PsiClass)parent).isInheritor((PsiClass)qualifierClass, true)) {
                return true;
              }
            }
          }
        }
      }
      return false;
    }

    private boolean isInsideQualifierClass() {
      if (myPosition.getParent() instanceof PsiJavaCodeReferenceElement) {
        final PsiElement qualifier = ((PsiJavaCodeReferenceElement)myPosition.getParent()).getQualifier();
        if (qualifier instanceof PsiJavaCodeReferenceElement) {
          final PsiElement qualifierClass = ((PsiJavaCodeReferenceElement)qualifier).resolve();
          if (qualifierClass instanceof PsiClass) {
            PsiElement parent = myPosition;
            final PsiManager psiManager = myPosition.getManager();
            while ((parent = PsiTreeUtil.getParentOfType(parent, PsiClass.class, true)) != null) {
              if (psiManager.areElementsEquivalent(parent, qualifierClass)) {
                return true;
              }
            }
          }
        }
      }
      return false;
    }

