// 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.psi.formatter.java;

import com.intellij.formatting.*;
import com.intellij.formatting.alignment.AlignmentStrategy;
import com.intellij.lang.ASTNode;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.formatter.FormatterUtil;
import com.intellij.psi.formatter.common.AbstractBlock;
import com.intellij.psi.formatter.java.wrap.ReservedWrapsProvider;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.codeStyle.ShiftIndentInsideHelper;
import com.intellij.psi.impl.source.tree.*;
import com.intellij.psi.impl.source.tree.java.ClassElement;
import com.intellij.psi.javadoc.PsiMarkdownCodeBlock;
import com.intellij.psi.jsp.JspClassLevelDeclarationStatementType;
import com.intellij.psi.jsp.JspCodeBlockType;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.SmartList;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

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

import static com.intellij.psi.formatter.java.JavaFormatterUtil.getWrapType;
import static com.intellij.psi.formatter.java.MultipleFieldDeclarationHelper.findLastFieldInGroup;

public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlock, ReservedWrapsProvider {
  private static final Logger LOG = Logger.getInstance(AbstractJavaBlock.class);

  protected final @NotNull CommonCodeStyleSettings mySettings;
  protected final @NotNull JavaCodeStyleSettings myJavaSettings;
  protected final CommonCodeStyleSettings.IndentOptions myIndentSettings;
  private final Indent myIndent;
  protected Indent myChildIndent;
  protected Alignment myChildAlignment;
  protected boolean myUseChildAttributes;
  protected final @NotNull AlignmentStrategy myAlignmentStrategy;
  private boolean myIsAfterClassKeyword;

  protected Alignment myReservedAlignment;
  protected Alignment myReservedAlignment2;

  private Map<IElementType, Wrap> myPreferredWraps;
  private AbstractJavaBlock myParentBlock;

  private final FormattingMode myFormattingMode;

  private final BlockFactory myBlockFactory = new BlockFactory() {
    @Override
    public Block createBlock(ASTNode node, Indent indent, Alignment alignment, Wrap wrap, @NotNull FormattingMode formattingMode) {
      return new SimpleJavaBlock(node, wrap, AlignmentStrategy.wrap(alignment), indent, mySettings, myJavaSettings, formattingMode);
    }

    @Override
    public CommonCodeStyleSettings getSettings() {
      return mySettings;
    }

    @Override
    public JavaCodeStyleSettings getJavaSettings() {
      return myJavaSettings;
    }

    @Override
    public FormattingMode getFormattingMode() {
      return myFormattingMode;
    }
  };

  protected AbstractJavaBlock(final @NotNull ASTNode node,
                              final Wrap wrap,
                              final Alignment alignment,
                              final Indent indent,
                              final @NotNull CommonCodeStyleSettings settings,
                              @NotNull JavaCodeStyleSettings javaSettings,
                              @NotNull FormattingMode formattingMode)
  {
    this(node, wrap, indent, settings, javaSettings, AlignmentStrategy.wrap(alignment), formattingMode);
  }

  protected AbstractJavaBlock(final @NotNull ASTNode node,
                              final Wrap wrap,
                              final @NotNull AlignmentStrategy alignmentStrategy,
                              final Indent indent,
                              final @NotNull CommonCodeStyleSettings settings,
                              @NotNull JavaCodeStyleSettings javaSettings,
                              @NotNull FormattingMode formattingMode)
  {
    this(node, wrap, indent, settings, javaSettings, alignmentStrategy, formattingMode);
  }

  private AbstractJavaBlock(@NotNull ASTNode ignored,
                            @NotNull CommonCodeStyleSettings commonSettings,
                            @NotNull JavaCodeStyleSettings javaSettings,
                            @NotNull FormattingMode formattingMode) {
    super(ignored, null, null);
    mySettings = commonSettings;
    myJavaSettings = javaSettings;
    myIndentSettings = commonSettings.getIndentOptions();
    myIndent = null;
    myAlignmentStrategy = AlignmentStrategy.getNullStrategy();
    myFormattingMode = formattingMode;
  }

  protected AbstractJavaBlock(final @NotNull ASTNode node,
                              final Wrap wrap,
                              final Indent indent,
                              final @NotNull CommonCodeStyleSettings settings,
                              @NotNull JavaCodeStyleSettings javaSettings,
                              final @NotNull AlignmentStrategy alignmentStrategy,
                              final @NotNull FormattingMode formattingMode) {
    super(node, wrap, createBlockAlignment(alignmentStrategy, node));
    mySettings = settings;
    myJavaSettings = javaSettings;
    myIndentSettings = settings.getIndentOptions();
    myIndent = indent;
    myAlignmentStrategy = alignmentStrategy;
    myFormattingMode = formattingMode;
  }

  private static @Nullable Alignment createBlockAlignment(@NotNull AlignmentStrategy strategy, @NotNull ASTNode node) {
    // There is a possible case that 'implements' section is incomplete (e.g. ends with comma). We may want to align lbrace
    // to the first implemented interface reference then.
    if (node.getElementType() == JavaElementType.IMPLEMENTS_LIST) {
      return null;
    }
    return strategy.getAlignment(node.getElementType());
  }

  public @NotNull Block createJavaBlock(@NotNull ASTNode child,
                                        @NotNull CommonCodeStyleSettings settings,
                                        @NotNull JavaCodeStyleSettings javaSettings,
                                        @Nullable Indent indent,
                                        @Nullable Wrap wrap,
                                        Alignment alignment,
                                        @NotNull FormattingMode formattingMode) {
    return createJavaBlock(child, settings, javaSettings,indent, wrap, AlignmentStrategy.wrap(alignment), formattingMode);
  }

  public @NotNull Block createJavaBlock(@NotNull ASTNode child,
                                        @NotNull CommonCodeStyleSettings settings,
                                        @NotNull JavaCodeStyleSettings javaSettings,
                                        final Indent indent,
                                        @Nullable Wrap wrap,
                                        @NotNull AlignmentStrategy alignmentStrategy,
                                        @NotNull FormattingMode formattingMode) {
    return createJavaBlock(child, settings, javaSettings, indent, wrap, alignmentStrategy, -1, formattingMode);
  }

  private @NotNull Block createJavaBlock(@NotNull ASTNode child,
                                         @NotNull CommonCodeStyleSettings settings,
                                         @NotNull JavaCodeStyleSettings javaSettings,
                                         @Nullable Indent indent,
                                         Wrap wrap,
                                         @NotNull AlignmentStrategy alignmentStrategy,
                                         int startOffset,
                                         @NotNull FormattingMode formattingMode) {
    Indent actualIndent = indent == null ? getDefaultSubtreeIndent(child, settings, javaSettings) : indent;
    IElementType elementType = child.getElementType();
    Alignment alignment = alignmentStrategy.getAlignment(elementType);
    PsiElement childPsi = child.getPsi();

    if (childPsi instanceof PsiWhiteSpace) {
      String text = child.getText();
      int start = CharArrayUtil.shiftForward(text, 0, " \t\n");
      int end = CharArrayUtil.shiftBackward(text, text.length() - 1, " \t\n") + 1;
      LOG.assertTrue(start < end);
      TextRange range = new TextRange(start + child.getStartOffset(), end + child.getStartOffset());
      return new PartialWhitespaceBlock(child, range, wrap, alignment, actualIndent, settings, javaSettings, myFormattingMode);
    }

    if (childPsi instanceof PsiImplicitClass) {
      return new SimpleJavaBlock(child, wrap, alignmentStrategy, Indent.getNoneIndent(), settings, javaSettings, formattingMode);
    }
    if (childPsi instanceof PsiClass || childPsi instanceof PsiJavaModule) {
      return new CodeBlockBlock(child, wrap, alignment, actualIndent, settings, javaSettings, formattingMode);
    }
    if (child.getElementType() == JavaElementType.METHOD) {
      return new BlockContainingJavaBlock(child, actualIndent, alignmentStrategy, mySettings, myJavaSettings, formattingMode);
    }
    if (isBlockType(elementType)) {
      return new BlockContainingJavaBlock(child, wrap, alignment, actualIndent, settings, javaSettings, formattingMode);
    }
    if (isStatement(child, child.getTreeParent())) {
      return new CodeBlockBlock(child, wrap, alignment, actualIndent, settings, javaSettings, myFormattingMode);
    }
    if (!isBuildIndentsOnly() && child instanceof PsiComment && child instanceof PsiLanguageInjectionHost psiLanguageInjectionHost) {
      InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(childPsi.getProject());
      if (injectedLanguageManager.getInjectedPsiFiles(psiLanguageInjectionHost) != null) {
        return new CommentWithInjectionBlock(child, wrap, alignment, indent, settings, javaSettings, formattingMode);
      }
    }
    if (child instanceof LeafElement || child instanceof PsiMarkdownCodeBlock) {
      if (child.getElementType() == JavaTokenType.C_STYLE_COMMENT) {
        return new CStyleCommentBlock(child, actualIndent);
      }
      LeafBlock block = new LeafBlock(child, wrap, alignment, actualIndent);
      block.setStartOffset(startOffset);
      return block;
    }
    if (isLikeExtendsList(elementType)) {
      return new ExtendsListBlock(child, wrap, alignmentStrategy, settings, javaSettings, formattingMode);
    }
    if (elementType == JavaElementType.CODE_BLOCK) {
      return new CodeBlockBlock(child, wrap, alignment, actualIndent, settings, javaSettings, formattingMode);
    }
    if (elementType == JavaElementType.LABELED_STATEMENT) {
      return new LabeledJavaBlock(child, wrap, alignment, actualIndent, settings, javaSettings, formattingMode);
    }
    if (JavaDocElementType.DOC_COMMENT_TOKENS.contains(elementType)) {
      return new DocCommentBlock(child, wrap, alignment, actualIndent, settings, javaSettings, formattingMode);
    }
    if (isTextBlock(childPsi)) {
      return new TextBlockBlock(child, wrap, alignmentStrategy, actualIndent, settings, javaSettings, formattingMode);
    }

    SimpleJavaBlock simpleJavaBlock = new SimpleJavaBlock(child, wrap, alignmentStrategy, actualIndent, settings, javaSettings, myFormattingMode);
    simpleJavaBlock.setStartOffset(startOffset);
    return simpleJavaBlock;
  }

  public static @NotNull Block newJavaBlock(@NotNull ASTNode child,
                                            @NotNull CommonCodeStyleSettings settings,
                                            @NotNull JavaCodeStyleSettings javaSettings,
                                            @NotNull FormattingMode formattingMode) {
    final Indent indent = getDefaultSubtreeIndent(child, settings, javaSettings);
    return newJavaBlock(child, settings, javaSettings, indent, null, AlignmentStrategy.getNullStrategy(), formattingMode);
  }

  public static @NotNull Block newJavaBlock(@NotNull ASTNode child,
                                            @NotNull CommonCodeStyleSettings settings,
                                            @NotNull JavaCodeStyleSettings javaSettings,
                                            @Nullable Indent indent,
                                            @Nullable Wrap wrap,
                                            @NotNull AlignmentStrategy strategy,
                                            @NotNull FormattingMode formattingMode) {
    return new AbstractJavaBlock(child, settings, javaSettings, formattingMode) {
      @Override
      protected List<Block> buildChildren() {
        return null;
      }
    }.createJavaBlock(child, settings, javaSettings, indent, wrap, strategy, formattingMode);
  }

  private static @NotNull CommonCodeStyleSettings.IndentOptions getJavaIndentOptions(CommonCodeStyleSettings settings) {
    CommonCodeStyleSettings.IndentOptions indentOptions = settings.getIndentOptions();
    assert indentOptions != null : "Java indent options are not initialized";
    return indentOptions;
  }

  private static boolean isLikeExtendsList(final IElementType elementType) {
    return elementType == JavaElementType.EXTENDS_LIST
           || elementType == JavaElementType.IMPLEMENTS_LIST
           || elementType == JavaElementType.THROWS_LIST
           || elementType == JavaElementType.PERMITS_LIST;
  }

  private static boolean isBlockType(final IElementType elementType) {
    return elementType == JavaElementType.SWITCH_STATEMENT
           || elementType == JavaElementType.FOR_STATEMENT
           || elementType == JavaElementType.WHILE_STATEMENT
           || elementType == JavaElementType.DO_WHILE_STATEMENT
           || elementType == JavaElementType.TRY_STATEMENT
           || elementType == JavaElementType.CATCH_SECTION
           || elementType == JavaElementType.IF_STATEMENT
           || elementType == JavaElementType.METHOD
           || elementType == JavaElementType.ARRAY_INITIALIZER_EXPRESSION
           || elementType == JavaElementType.ANNOTATION_ARRAY_INITIALIZER
           || elementType == JavaElementType.CLASS_INITIALIZER
           || elementType == JavaElementType.SYNCHRONIZED_STATEMENT
           || elementType == JavaElementType.SWITCH_LABELED_RULE
           || elementType == JavaElementType.FOREACH_STATEMENT;
  }


  private static @Nullable Indent getDefaultSubtreeIndent(@NotNull ASTNode child, @NotNull CommonCodeStyleSettings settings, @NotNull JavaCodeStyleSettings javaSettings) {
    CommonCodeStyleSettings.IndentOptions indentOptions= getJavaIndentOptions(settings);
    final ASTNode parent = child.getTreeParent();
    final IElementType childNodeType = child.getElementType();
    if (childNodeType == JavaElementType.ANNOTATION) {
      if (parent.getPsi() instanceof PsiArrayInitializerMemberValue) {
        return Indent.getNormalIndent();
      } else if (JavaFormatterRecordUtil.shouldAdjustIndentForRecordComponentChild(child, javaSettings)) {
        return Indent.getContinuationIndent();
      }
      return Indent.getNoneIndent();
    }

    final ASTNode prevElement = skipCommentsAndWhitespacesBackwards(child);
    if (prevElement != null) {
      if (JavaFormatterRecordUtil.shouldAdjustIndentForRecordComponentChild(child, javaSettings)) {
        return Indent.getContinuationIndent();
      }
      else if (prevElement.getElementType() == JavaElementType.MODIFIER_LIST) {
        return Indent.getNoneIndent();
      }
    }

    if (childNodeType == JavaDocElementType.DOC_TAG) return Indent.getNoneIndent();
    if (childNodeType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS) {
      if(PsiUtil.isInMarkdownDocComment(child.getPsi())) {
        return Indent.getNoneIndent();
      }
      return Indent.getSpaceIndent(1);
    }
    if (child.getPsi() instanceof PsiFile) return Indent.getNoneIndent();
    if (parent != null) {
      final Indent defaultChildIndent = getChildIndent(parent, indentOptions);
      if (defaultChildIndent != null) return defaultChildIndent;
      if (parent.getPsi() instanceof PsiLambdaExpression && child instanceof PsiCodeBlock) {
        if (settings.LAMBDA_BRACE_STYLE == CommonCodeStyleSettings.NEXT_LINE_SHIFTED ||
            settings.LAMBDA_BRACE_STYLE == CommonCodeStyleSettings.NEXT_LINE_SHIFTED2) {
          return Indent.getNormalIndent();
        }
        return Indent.getNoneIndent();
      }
      if (parent.getPsi() instanceof PsiSwitchExpression &&
          child instanceof PsiCodeBlock) {
        if (settings.BRACE_STYLE == CommonCodeStyleSettings.NEXT_LINE_SHIFTED ||
            settings.BRACE_STYLE == CommonCodeStyleSettings.NEXT_LINE_SHIFTED2) {
          return Indent.getNormalIndent();
        }
        return Indent.getNoneIndent();
      }

      if (JavaFormatterConditionalExpressionUtil.isInsideConditionalExpression(parent)) {
        return Indent.getSpaceIndent(0, true);
      }
    }

    return null;
  }

  private static @Nullable ASTNode skipParenthesesUp(@NotNull ASTNode node) {
    ASTNode currNode = node.getTreeParent();
    while (currNode != null && currNode.getElementType() == JavaElementType.PARENTH_EXPRESSION) {
      currNode = currNode.getTreeParent();
    }
    return currNode;
  }

  private static @Nullable ASTNode skipCommentsAndWhitespacesBackwards(@NotNull ASTNode node) {
    ASTNode currNode = node.getTreePrev();
    while (currNode != null &&
           (currNode.getElementType() == JavaTokenType.END_OF_LINE_COMMENT || FormatterUtil.isWhitespaceOrEmpty(currNode))) {
      currNode = currNode.getTreePrev();
    }
    return currNode;
  }

  private static @Nullable Indent getChildIndent(@NotNull ASTNode parent, @NotNull CommonCodeStyleSettings.IndentOptions indentOptions) {
    final IElementType parentType = parent.getElementType();
    if (parentType == JavaElementType.MODIFIER_LIST) return Indent.getNoneIndent();
    if (parentType instanceof JspCodeBlockType) return Indent.getNormalIndent();
    if (parentType instanceof JspClassLevelDeclarationStatementType) return Indent.getNormalIndent();
    if (parentType == TokenType.DUMMY_HOLDER) return Indent.getNoneIndent();
    if (parentType == JavaElementType.CLASS) return Indent.getNoneIndent();
    if (parentType == JavaElementType.IF_STATEMENT) return Indent.getNoneIndent();
    if (parentType == JavaElementType.TRY_STATEMENT) return Indent.getNoneIndent();
    if (parentType == JavaElementType.CATCH_SECTION) return Indent.getNoneIndent();
    if (parentType == JavaElementType.FOR_STATEMENT) return Indent.getNoneIndent();
    if (parentType == JavaElementType.FOREACH_STATEMENT) return Indent.getNoneIndent();
    if (parentType == JavaElementType.BLOCK_STATEMENT) return Indent.getNoneIndent();
    if (parentType == JavaElementType.DO_WHILE_STATEMENT) return Indent.getNoneIndent();
    if (parentType == JavaElementType.WHILE_STATEMENT) return Indent.getNoneIndent();
    if (parentType == JavaElementType.SWITCH_STATEMENT) return Indent.getNoneIndent();
    if (parentType == JavaElementType.METHOD) return Indent.getNoneIndent();
    if (parentType == JavaDocElementType.DOC_COMMENT) return Indent.getNoneIndent();
    if (parentType == JavaDocElementType.DOC_MARKDOWN_COMMENT) return Indent.getNoneIndent();
    if (parentType == JavaDocElementType.DOC_TAG) return Indent.getNoneIndent();
    if (parentType == JavaDocElementType.DOC_INLINE_TAG) return Indent.getNoneIndent();
    if (parentType == JavaElementType.IMPORT_LIST) return Indent.getNoneIndent();
    if (parentType == JavaElementType.FIELD) return Indent.getContinuationWithoutFirstIndent(indentOptions.USE_RELATIVE_INDENTS);
    if (parentType == JavaElementType.EXPRESSION_STATEMENT) return Indent.getNoneIndent();
    if (parentType == JavaElementType.IMPLICIT_CLASS) return Indent.getNoneIndent();
    if (parentType == JavaElementType.RECORD_COMPONENT) return Indent.getNoneIndent();
    if (parentType == JavaElementType.PACKAGE_STATEMENT) return Indent.getNoneIndent();
    if (SourceTreeToPsiMap.treeElementToPsi(parent) instanceof PsiFile) {
      return Indent.getNoneIndent();
    }
    return null;
  }

  protected static boolean isRBrace(final @NotNull ASTNode child) {
    return child.getElementType() == JavaTokenType.RBRACE;
  }

  @Override
  public @Nullable Spacing getSpacing(Block child1, @NotNull Block child2) {
    return JavaSpacePropertyProcessor.getSpacing(child2, mySettings, myJavaSettings);
  }

  @Override
  public ASTNode getFirstTreeNode() {
    return myNode;
  }

  @Override
  public Indent getIndent() {
    return myIndent;
  }

  protected static boolean isStatement(final ASTNode child, final @Nullable ASTNode parentNode) {
    if (parentNode != null) {
      final IElementType parentType = parentNode.getElementType();
      if (parentType == JavaElementType.CODE_BLOCK) return false;
      final int role = ((CompositeElement)parentNode).getChildRole(child);
      if (parentType == JavaElementType.IF_STATEMENT) return role == ChildRole.THEN_BRANCH || role == ChildRole.ELSE_BRANCH;
      if (parentType == JavaElementType.FOR_STATEMENT) return role == ChildRole.LOOP_BODY;
      if (parentType == JavaElementType.WHILE_STATEMENT) return role == ChildRole.LOOP_BODY;
      if (parentType == JavaElementType.DO_WHILE_STATEMENT) return role == ChildRole.LOOP_BODY;
      if (parentType == JavaElementType.FOREACH_STATEMENT) return role == ChildRole.LOOP_BODY;
    }
    return false;
  }

  protected @Nullable Wrap createChildWrap() {
    //when detecting indent we do not care about wraps
    return isBuildIndentsOnly() ? null : JavaFormatterUtil.createDefaultWrap(this, mySettings, myJavaSettings, this);
  }

  protected @Nullable Alignment createChildAlignment() {
    IElementType nodeType = myNode.getElementType();
    if (nodeType == JavaElementType.POLYADIC_EXPRESSION) nodeType = JavaElementType.BINARY_EXPRESSION;
    if (nodeType == JavaElementType.ASSIGNMENT_EXPRESSION) {
      if (myNode.getTreeParent() != null
          && myNode.getTreeParent().getElementType() == JavaElementType.ASSIGNMENT_EXPRESSION
          && myAlignment != null) {
        return myAlignment;
      }
      return createAlignment(mySettings.ALIGN_MULTILINE_ASSIGNMENT, null);
    }
    if (nodeType == JavaElementType.PARENTH_EXPRESSION) {
      return createAlignment(mySettings.ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION, null);
    }
    if (nodeType == JavaElementType.CONDITIONAL_EXPRESSION) {
      return createAlignment(mySettings.ALIGN_MULTILINE_TERNARY_OPERATION, null);
    }
    if (nodeType == JavaElementType.FOR_STATEMENT) {
      return createAlignment(mySettings.ALIGN_MULTILINE_FOR, null);
    }
    if (nodeType == JavaElementType.EXTENDS_LIST ||
        nodeType == JavaElementType.IMPLEMENTS_LIST ||
        nodeType == JavaElementType.PERMITS_LIST) {
      return createAlignment(mySettings.ALIGN_MULTILINE_EXTENDS_LIST, null);
    }
    if (nodeType == JavaElementType.THROWS_LIST) {
      return createAlignment(mySettings.ALIGN_MULTILINE_THROWS_LIST, null);
    }
    if (nodeType == JavaElementType.PARAMETER_LIST) {
      return createAlignment(mySettings.ALIGN_MULTILINE_PARAMETERS, null);
    }
    if (nodeType == JavaElementType.RESOURCE_LIST) {
      return createAlignment(mySettings.ALIGN_MULTILINE_RESOURCES, null);
    }
    if (nodeType == JavaElementType.BINARY_EXPRESSION) {
      Alignment defaultAlignment = null;
      if (shouldInheritAlignment()) {
        defaultAlignment = myAlignment;
      }
      return createAlignment(mySettings.ALIGN_MULTILINE_BINARY_OPERATION, defaultAlignment);
    }
    if (JavaFormatterUtil.isTopLevelTypeInCatchSection(nodeType, myNode)) {
      return createAlignment(myJavaSettings.ALIGN_TYPES_IN_MULTI_CATCH, null);
    }
    if (isTextBlock(myNode.getPsi())) {
        return createAlignment(myJavaSettings.ALIGN_MULTILINE_TEXT_BLOCKS,null);
    }
    return null;
  }

  protected @Nullable Alignment chooseAlignment(@Nullable Alignment alignment, @Nullable Alignment alignment2, @NotNull ASTNode child) {
    if (isTernaryOperatorToken(child)) {
      return alignment2;
    }
    return alignment;
  }

  private boolean isTernaryOperatorToken(final @NotNull ASTNode child) {
    final IElementType nodeType = myNode.getElementType();

    if (nodeType == JavaElementType.CONDITIONAL_EXPRESSION) {
      IElementType childType = child.getElementType();
      return childType == JavaTokenType.QUEST || childType ==JavaTokenType.COLON;
    }
    else {
      return false;
    }
  }

  private static boolean isTextBlock(@NotNull PsiElement childPsi) {
    PsiLiteralExpression literal = ObjectUtils.tryCast(childPsi, PsiLiteralExpression.class);
    return literal != null && literal.isTextBlock();
  }

  private boolean shouldInheritAlignment() {
    if (myNode instanceof PsiPolyadicExpression) {
      final ASTNode treeParent = myNode.getTreeParent();
      if (treeParent instanceof PsiPolyadicExpression) {
        return JavaFormatterUtil.areSamePriorityBinaryExpressions(myNode, treeParent);
      }
    }
    return false;
  }


  protected @Nullable ASTNode processChild(final @NotNull List<Block> result,
                                           @NotNull ASTNode child,
                                           Alignment defaultAlignment,
                                           final Wrap defaultWrap,
                                           final Indent childIndent) {
    return processChild(result, child, AlignmentStrategy.wrap(defaultAlignment), defaultWrap, childIndent, -1);
  }

  protected @Nullable ASTNode processChild(final @NotNull List<Block> result,
                                           @NotNull ASTNode child,
                                           @NotNull AlignmentStrategy alignmentStrategy,
                                           final @Nullable Wrap defaultWrap,
                                           final Indent childIndent) {
    return processChild(result, child, alignmentStrategy, defaultWrap, childIndent, -1);
  }

  /**
   * Creates blocks for nodes starting from child.
   * @param result mutable list of child blocks
   * @param child current child to create block for
   * @param defaultWrap default wrap returned by {@link AbstractJavaBlock#createChildWrap() }
   * @return next node to handle
   */
  protected @Nullable ASTNode processChild(final @NotNull List<Block> result,
                                 @NotNull ASTNode child,
                                 @NotNull AlignmentStrategy alignmentStrategy,
                                 final Wrap defaultWrap,
                                 Indent childIndent,
                                 int childOffset) {
    final IElementType childType = child.getElementType();
    if (childType == JavaTokenType.CLASS_KEYWORD || childType == JavaTokenType.INTERFACE_KEYWORD) {
      myIsAfterClassKeyword = true;
    }
    if (childType == JavaElementType.METHOD_CALL_EXPRESSION) {
      Alignment alignment = shouldAlignChild(child) ? alignmentStrategy.getAlignment(childType) : null;
      result.add(createMethodCallExpressionBlock(child, arrangeChildWrap(child, defaultWrap), alignment, childIndent));
    }
    else {
      IElementType nodeType = myNode.getElementType();
      if (nodeType == JavaElementType.POLYADIC_EXPRESSION) nodeType = JavaElementType.BINARY_EXPRESSION;

      if (childType == JavaTokenType.LBRACE && nodeType == JavaElementType.ARRAY_INITIALIZER_EXPRESSION) {
        ArrayInitializerBlocksBuilder builder = new ArrayInitializerBlocksBuilder(myNode, myBlockFactory);
        List<Block> newlyCreated = builder.buildBlocks();

        child = myNode.getLastChildNode();
        result.addAll(newlyCreated);
      }
      else if (childType == JavaTokenType.LBRACE && nodeType == JavaElementType.ANNOTATION_ARRAY_INITIALIZER) {
        final Wrap wrap = Wrap.createWrap(getWrapType(mySettings.ARRAY_INITIALIZER_WRAP), false);
        child = processParenthesisBlock(JavaTokenType.LBRACE, JavaTokenType.RBRACE,
                                        result,
                                        child,
                                        WrappingStrategy.createDoNotWrapCommaStrategy(wrap),
                                        mySettings.ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION,
                                        JavaTokenType.COMMA);
      }
      else if (childType == JavaTokenType.LPARENTH && nodeType == JavaElementType.EXPRESSION_LIST) {
        final Wrap wrap = Wrap.createWrap(getWrapType(mySettings.CALL_PARAMETERS_WRAP), false);
        if (mySettings.PREFER_PARAMETERS_WRAP && !isInsideMethodCall(myNode.getPsi())) {
            wrap.ignoreParentWraps();
        }
        child = processParenthesisBlock(result, child,
                                        WrappingStrategy.createDoNotWrapCommaAndCommentStrategy(wrap),
                                        mySettings.ALIGN_MULTILINE_PARAMETERS_IN_CALLS,
                                        JavaTokenType.COMMA, JavaTokenType.END_OF_LINE_COMMENT, JavaTokenType.C_STYLE_COMMENT);
      }
      else if (childType == JavaTokenType.LPARENTH && nodeType == JavaElementType.PARAMETER_LIST) {
        ASTNode parent = myNode.getTreeParent();
        boolean isLambdaParameterList = parent != null && parent.getElementType() == JavaElementType.LAMBDA_EXPRESSION;
        Wrap wrapToUse = isLambdaParameterList ? null : getMethodParametersWrap();
        WrappingStrategy wrapStrategy = WrappingStrategy.createDoNotWrapCommaStrategy(wrapToUse);
        child = processParenthesisBlock(result, child, wrapStrategy, mySettings.ALIGN_MULTILINE_PARAMETERS);
      }
      else if (childType == JavaTokenType.LPARENTH && nodeType == JavaElementType.RECORD_HEADER) {
        Wrap wrap = Wrap.createWrap(getWrapType(myJavaSettings.RECORD_COMPONENTS_WRAP), false);
        WrappingStrategy wrapStrategy = WrappingStrategy.createDoNotWrapCommaStrategy(wrap);
        child = processParenthesisBlock(result, child, wrapStrategy, myJavaSettings.ALIGN_MULTILINE_RECORDS);
      }
      else if (childType == JavaTokenType.LPARENTH && nodeType == JavaElementType.DECONSTRUCTION_LIST) {
        Wrap wrap = Wrap.createWrap(getWrapType(myJavaSettings.DECONSTRUCTION_LIST_WRAP), false);
        WrappingStrategy wrapStrategy = WrappingStrategy.createDoNotWrapCommaStrategy(wrap);
        child = processParenthesisBlock(result, child, wrapStrategy, myJavaSettings.ALIGN_MULTILINE_DECONSTRUCTION_LIST_COMPONENTS);
      }
      else if (childType == JavaTokenType.LPARENTH && nodeType == JavaElementType.RESOURCE_LIST) {
        Wrap wrap = Wrap.createWrap(getWrapType(mySettings.RESOURCE_LIST_WRAP), false);
        child = processParenthesisBlock(result, child,
                                  WrappingStrategy.createDoNotWrapCommaStrategy(wrap),
                                  mySettings.ALIGN_MULTILINE_RESOURCES);
      }
      else if (childType == JavaTokenType.LPARENTH && nodeType == JavaElementType.ANNOTATION_PARAMETER_LIST) {
        AnnotationInitializerBlocksBuilder builder = new AnnotationInitializerBlocksBuilder(myNode, myBlockFactory);
        List<Block> newlyCreated = builder.buildBlocks();

        child = myNode.getLastChildNode();
        result.addAll(newlyCreated);
      }
      else if (childType == JavaTokenType.OR && nodeType == JavaElementType.TYPE) {
        // by default in "Foo | Bar | Baz" each child will have same (default) wrap, but we need to distinguish types and "|"
        Wrap wrap = Wrap.createWrap(WrapType.NONE, false);
        result.add(new SimpleJavaBlock(child, wrap, alignmentStrategy, childIndent, mySettings, myJavaSettings, myFormattingMode));
      }
      else if (childType == JavaTokenType.LPARENTH && nodeType == JavaElementType.PARENTH_EXPRESSION) {
        child = processParenthesisBlock(result, child,
                                  WrappingStrategy.DO_NOT_WRAP,
                                  mySettings.ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION);
      }
      else if (childType == JavaElementType.ENUM_CONSTANT && myNode instanceof ClassElement classElement) {
        child = processEnumBlock(result, child, classElement.findEnumConstantListDelimiterPlace());
      }
      else if (mySettings.TERNARY_OPERATION_SIGNS_ON_NEXT_LINE && isTernaryOperationSign(child)) {
        child = processTernaryOperationRange(result, child, defaultWrap, childIndent);
      }
      else if (childType == JavaElementType.FIELD) {
