@Nullable
    static ThenElse from(@NotNull PsiIfStatement ifStatement,
                         PsiStatement @NotNull [] thenBranch,
                         PsiStatement @NotNull [] elseBranch,
                         boolean isOnTheFly) {
      LocalEquivalenceChecker equivalence = getChecker(thenBranch, elseBranch);

      int thenLen = thenBranch.length;
      int elseLen = elseBranch.length;
      int minStmtCount = Math.min(thenLen, elseLen);

      PsiExpression condition = ifStatement.getCondition();
      if (condition == null) return null;

      boolean conditionHasSideEffects = SideEffectChecker.mayHaveSideEffects(condition);
      if (!isOnTheFly && conditionHasSideEffects) return null;
      List<PsiLocalVariable> conditionVariables = new ArrayList<>();
      boolean conditionVariablesCantBeChangedTransitively = StreamEx.ofTree((PsiElement)condition, el -> StreamEx.of(el.getChildren()))
        .allMatch(element -> {
          if (!(element instanceof PsiReferenceExpression)) {
            return !(element instanceof PsiMethodCallExpression);
          }
          PsiLocalVariable localVariable = tryCast(((PsiReferenceExpression)element).resolve(), PsiLocalVariable.class);
          if (localVariable == null) return false;
          conditionVariables.add(localVariable);
          return true;
        });

      List<ExtractionUnit> headCommonParts = new ArrayList<>();
      Set<PsiVariable> extractedVariables = new HashSet<>();
      Set<PsiVariable> notEquivalentVariableDeclarations = new HashSet<>(0);

      extractHeadCommonParts(thenBranch, elseBranch, isOnTheFly, equivalence, minStmtCount, conditionVariables, headCommonParts,
                             extractedVariables, notEquivalentVariableDeclarations);
      if (headCommonParts.isEmpty()) {
        // when head common parts contains e.g. first variable with different names but changes semantics and batch mode is on
        equivalence.mySubstitutionTable.clear();
      }

      // set of variables which have in one branch initializer, but in other branch doesn't have initializer
      final Set<PsiVariable> differentDeclarationsWithoutInitializers = StreamEx.of(headCommonParts)
        .select(VariableDeclarationUnit.class)
        .map(unit -> unit.getVariableWithoutInitializer())
        .nonNull()
        .toSet();

      int extractedFromStart = headCommonParts.size();
      int canBeExtractedFromThenTail = thenLen - extractedFromStart;
      int canBeExtractedFromElseTail = elseLen - extractedFromStart;
      int canBeExtractedFromTail = Math.min(canBeExtractedFromThenTail, canBeExtractedFromElseTail);
      List<PsiStatement> tailCommonParts = new ArrayList<>();
      extractTailCommonParts(ifStatement, thenBranch, elseBranch, equivalence, thenLen, elseLen, extractedVariables, canBeExtractedFromTail,
                             tailCommonParts, differentDeclarationsWithoutInitializers);

      tryAppendHeadPartsToTail(headCommonParts, canBeExtractedFromThenTail, canBeExtractedFromElseTail, canBeExtractedFromTail,
                               tailCommonParts);

      Map<PsiLocalVariable, String> substitutionTable = equivalence.mySubstitutionTable;
      if (uncommonElseStatementsContainsThenNames(elseBranch, elseLen, headCommonParts, tailCommonParts, substitutionTable)) {
        return null;
      }
      if (headCommonParts.isEmpty() && tailCommonParts.isEmpty()) return null;
      PsiStatement thenStatement = ifStatement.getThenBranch();
      PsiStatement elseStatement = ifStatement.getElseBranch();
      if (thenStatement == null || elseStatement == null) return null;
      final CommonPartType type = getType(headCommonParts, tailCommonParts, notEquivalentVariableDeclarations.isEmpty(), thenLen, elseLen,
                                          thenStatement, elseStatement);
      if (type == CommonPartType.VARIABLES_ONLY && !notEquivalentVariableDeclarations.isEmpty()) return null;
      boolean mayChangeSemantics =
        mayChangeSemantics(conditionHasSideEffects, conditionVariablesCantBeChangedTransitively, headCommonParts);
      return new ThenElse(headCommonParts, tailCommonParts, mayChangeSemantics, type, substitutionTable);
    }
@Nullable
    static ThenElse from(@NotNull PsiIfStatement ifStatement,
                         PsiStatement @NotNull [] thenBranch,
                         PsiStatement @NotNull [] elseBranch,
                         boolean isOnTheFly)