<?php

declare (strict_types=1);
/*
 * This file is part of PHP CS Fixer.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */
namespace PhpCsFixer\Fixer\Whitespace;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
use PhpCsFixer\Fixer\IndentationTrait;
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Analyzer\AlternativeSyntaxAnalyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\FCT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
 * @phpstan-type _AutogeneratedInputConfiguration array{
 *  stick_comment_to_next_continuous_control_statement?: bool,
 * }
 * @phpstan-type _AutogeneratedComputedConfiguration array{
 *  stick_comment_to_next_continuous_control_statement: bool,
 * }
 *
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
 *
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
 */
final class StatementIndentationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
{
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
    use ConfigurableFixerTrait;
    use IndentationTrait;
    private const BLOCK_SIGNATURE_FIRST_TOKENS = [\T_USE, \T_IF, \T_ELSE, \T_ELSEIF, \T_FOR, \T_FOREACH, \T_WHILE, \T_DO, \T_SWITCH, \T_CASE, \T_DEFAULT, \T_TRY, \T_CLASS, \T_INTERFACE, \T_TRAIT, \T_EXTENDS, \T_IMPLEMENTS, \T_CONST, FCT::T_MATCH];
    private const CONTROL_STRUCTURE_POSSIBIBLY_WITHOUT_BRACES_TOKENS = [\T_IF, \T_ELSE, \T_ELSEIF, \T_FOR, \T_FOREACH, \T_WHILE, \T_DO];
    private const BLOCK_FIRST_TOKENS = ['{', [CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN], [CT::T_USE_TRAIT], [CT::T_GROUP_IMPORT_BRACE_OPEN], [CT::T_PROPERTY_HOOK_BRACE_OPEN], [FCT::T_ATTRIBUTE]];
    private const PROPERTY_KEYWORDS = [\T_VAR, \T_PUBLIC, \T_PROTECTED, \T_PRIVATE, \T_STATIC, FCT::T_READONLY];
    /**
     * @var \PhpCsFixer\Tokenizer\Analyzer\AlternativeSyntaxAnalyzer
     */
    private $alternativeSyntaxAnalyzer;
    /**
     * @var bool
     */
    private $bracesFixerCompatibility;
    public function __construct(bool $bracesFixerCompatibility = \false)
    {
        parent::__construct();
        $this->bracesFixerCompatibility = $bracesFixerCompatibility;
    }
    public function getDefinition() : FixerDefinitionInterface
    {
        return new FixerDefinition('Each statement must be indented.', [new CodeSample(<<<'PHP'
<?php

namespace ECSPrefix202510;

if ($baz == \true) {
    echo "foo";
} else {
    echo "bar";
}

PHP
), new CodeSample(<<<'PHP'
<?php

namespace ECSPrefix202510;

// foo
if ($foo) {
    echo "foo";
    // this is treated as comment of `if` block, as `stick_comment_to_next_continuous_control_statement` is disabled
} else {
    $aaa = 1;
}

PHP
, ['stick_comment_to_next_continuous_control_statement' => \false]), new CodeSample(<<<'PHP'
<?php

namespace ECSPrefix202510;

// foo
if ($foo) {
    echo "foo";
    // this is treated as comment of `elseif(1)` block, as `stick_comment_to_next_continuous_control_statement` is enabled
} elseif (1) {
    echo "bar";
} elseif (2) {
    // this is treated as comment of `elseif(2)` block, as the only content of that block
} elseif (3) {
    $aaa = 1;
    // this is treated as comment of `elseif(3)` block, as it is a comment in the final block
}

PHP
, ['stick_comment_to_next_continuous_control_statement' => \true])]);
    }
    /**
     * {@inheritdoc}
     *
     * Must run before HeredocIndentationFixer.
     * Must run after BracesPositionFixer, ClassAttributesSeparationFixer, CurlyBracesPositionFixer, FullyQualifiedStrictTypesFixer, GlobalNamespaceImportFixer, MethodArgumentSpaceFixer, NoUselessElseFixer, YieldFromArrayToYieldsFixer.
     */
    public function getPriority() : int
    {
        return -3;
    }
    public function isCandidate(Tokens $tokens) : bool
    {
        return \true;
    }
    protected function createConfigurationDefinition() : FixerConfigurationResolverInterface
    {
        return new FixerConfigurationResolver([(new FixerOptionBuilder('stick_comment_to_next_continuous_control_statement', 'Last comment of code block counts as comment for next block.'))->setAllowedTypes(['bool'])->setDefault(\false)->getOption()]);
    }
    protected function applyFix(\SplFileInfo $file, Tokens $tokens) : void
    {
        $this->alternativeSyntaxAnalyzer = new AlternativeSyntaxAnalyzer();
        $endIndex = \count($tokens) - 1;
        if ($tokens[$endIndex]->isWhitespace()) {
            --$endIndex;
        }
        $lastIndent = $this->getLineIndentationWithBracesCompatibility($tokens, 0, $this->extractIndent($this->computeNewLineContent($tokens, 0)));
        /**
         * @var list<array{
         *     type: 'block'|'block_signature'|'statement',
         *     skip: bool,
         *     end_index: int,
         *     end_index_inclusive: bool,
         *     initial_indent: string,
         *     new_indent?: string,
         *     is_indented_block: bool,
         * }> $scopes
         */
        $scopes = [['type' => 'block', 'skip' => \false, 'end_index' => $endIndex, 'end_index_inclusive' => \true, 'initial_indent' => $lastIndent, 'is_indented_block' => \false]];
        $previousLineInitialIndent = '';
        $previousLineNewIndent = '';
        $noBracesBlockStarts = [];
        $alternativeBlockStarts = [];
        $caseBlockStarts = [];
        foreach ($tokens as $index => $token) {
            $currentScope = \count($scopes) - 1;
            if (isset($noBracesBlockStarts[$index])) {
                $scopes[] = ['type' => 'block', 'skip' => \false, 'end_index' => $this->findStatementEndIndex($tokens, $index, \count($tokens) - 1) + 1, 'end_index_inclusive' => \true, 'initial_indent' => $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent), 'is_indented_block' => \true];
                ++$currentScope;
            }
            if ($token->equalsAny(self::BLOCK_FIRST_TOKENS) || $token->equals('(') && !$tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(\T_ARRAY) || isset($alternativeBlockStarts[$index]) || isset($caseBlockStarts[$index])) {
                $endIndexInclusive = \true;
                if ($token->isGivenKind([\T_EXTENDS, \T_IMPLEMENTS])) {
                    $endIndex = $tokens->getNextTokenOfKind($index, ['{']);
                } elseif ($token->isGivenKind(CT::T_USE_TRAIT)) {
                    $endIndex = $tokens->getNextTokenOfKind($index, [';']);
                } elseif ($token->equals(':')) {
                    if (isset($caseBlockStarts[$index])) {
                        [$endIndex, $endIndexInclusive] = $this->findCaseBlockEnd($tokens, $index);
                    } elseif ($this->alternativeSyntaxAnalyzer->belongsToAlternativeSyntax($tokens, $index)) {
                        $endIndex = $this->alternativeSyntaxAnalyzer->findAlternativeSyntaxBlockEnd($tokens, $alternativeBlockStarts[$index]);
                    }
                } elseif ($token->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN)) {
                    $endIndex = $tokens->getNextTokenOfKind($index, [[CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE]]);
                } elseif ($token->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) {
                    $endIndex = $tokens->getNextTokenOfKind($index, [[CT::T_GROUP_IMPORT_BRACE_CLOSE]]);
                } elseif ($token->equals('{')) {
                    $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
                } elseif ($token->equals('(')) {
                    $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
                } elseif ($token->isGivenKind(CT::T_PROPERTY_HOOK_BRACE_OPEN)) {
                    $endIndex = $tokens->getNextTokenOfKind($index, [[CT::T_PROPERTY_HOOK_BRACE_CLOSE]]);
                } else {
                    $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);
                }
                if ('block_signature' === $scopes[$currentScope]['type']) {
                    $initialIndent = $scopes[$currentScope]['initial_indent'];
                } else {
                    $initialIndent = $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent);
                }
                $skip = \false;
                if ($this->bracesFixerCompatibility) {
                    $prevIndex = $tokens->getPrevMeaningfulToken($index);
                    if (null !== $prevIndex) {
                        $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
                    }
                    if (null !== $prevIndex && $tokens[$prevIndex]->isGivenKind([\T_FUNCTION, \T_FN])) {
                        $skip = \true;
                    }
                }
                $scopes[] = ['type' => 'block', 'skip' => $skip, 'end_index' => $endIndex, 'end_index_inclusive' => $endIndexInclusive, 'initial_indent' => $initialIndent, 'is_indented_block' => \true];
                ++$currentScope;
                while ($index >= $scopes[$currentScope]['end_index']) {
                    \array_pop($scopes);
                    --$currentScope;
                }
                continue;
            }
            if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN) || $token->equals('(') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(\T_ARRAY)) {
                $blockType = $token->equals('(') ? Tokens::BLOCK_TYPE_PARENTHESIS_BRACE : Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE;
                $scopes[] = ['type' => 'statement', 'skip' => \true, 'end_index' => $tokens->findBlockEnd($blockType, $index), 'end_index_inclusive' => \true, 'initial_indent' => $previousLineInitialIndent, 'new_indent' => $previousLineNewIndent, 'is_indented_block' => \false];
                continue;
            }
            $isPropertyStart = $this->isPropertyStart($tokens, $index);
            if ($isPropertyStart || $token->isGivenKind(self::BLOCK_SIGNATURE_FIRST_TOKENS)) {
                $lastWhitespaceIndex = null;
                $closingParenthesisIndex = null;
                for ($endIndex = $index + 1, $max = \count($tokens); $endIndex < $max; ++$endIndex) {
                    $endToken = $tokens[$endIndex];
                    if ($endToken->equals('(')) {
                        $closingParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex);
                        $endIndex = $closingParenthesisIndex;
                        continue;
                    }
                    if ($endToken->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) {
                        $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $endIndex);
                        continue;
                    }
                    if ($endToken->equalsAny(['{', ';', [\T_DOUBLE_ARROW], [\T_IMPLEMENTS]])) {
                        break;
                    }
                    if ($endToken->equals(':')) {
                        if ($token->isGivenKind([\T_CASE, \T_DEFAULT])) {
                            $caseBlockStarts[$endIndex] = $index;
                        } else {
                            $alternativeBlockStarts[$endIndex] = $index;
                        }
                        break;
                    }
                    if (!$token->isGivenKind(self::CONTROL_STRUCTURE_POSSIBIBLY_WITHOUT_BRACES_TOKENS)) {
                        continue;
                    }
                    if ($endToken->isWhitespace()) {
                        $lastWhitespaceIndex = $endIndex;
                        continue;
                    }
                    if (!$endToken->isComment()) {
                        $noBraceBlockStartIndex = $lastWhitespaceIndex ?? $endIndex;
                        $noBracesBlockStarts[$noBraceBlockStartIndex] = \true;
                        $endIndex = $closingParenthesisIndex ?? $index;
                        break;
                    }
                }
                $scopes[] = ['type' => 'block_signature', 'skip' => \false, 'end_index' => $endIndex, 'end_index_inclusive' => \true, 'initial_indent' => $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent), 'is_indented_block' => $isPropertyStart || $token->isGivenKind([\T_EXTENDS, \T_IMPLEMENTS, \T_CONST])];
                continue;
            }
            if ($token->isGivenKind(\T_FUNCTION)) {
                $endIndex = $index + 1;
                for ($max = \count($tokens); $endIndex < $max; ++$endIndex) {
                    if ($tokens[$endIndex]->equals('(')) {
                        $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex);
                        continue;
                    }
                    if ($tokens[$endIndex]->equalsAny(['{', ';'])) {
                        break;
                    }
                }
                $scopes[] = ['type' => 'block_signature', 'skip' => \false, 'end_index' => $endIndex, 'end_index_inclusive' => \true, 'initial_indent' => $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent), 'is_indented_block' => \false];
                continue;
            }
            if ($token->isWhitespace() || $index > 0 && $tokens[$index - 1]->isGivenKind(\T_OPEN_TAG)) {
                $previousOpenTagContent = $tokens[$index - 1]->isGivenKind(\T_OPEN_TAG) ? Preg::replace('/\\S/', '', $tokens[$index - 1]->getContent()) : '';
                $content = $previousOpenTagContent . ($token->isWhitespace() ? $token->getContent() : '');
                if (!Preg::match('/\\R/', $content)) {
                    continue;
                }
                $nextToken = $tokens[$index + 1] ?? null;
                if ($this->bracesFixerCompatibility && null !== $nextToken && $nextToken->isComment() && !$this->isCommentWithFixableIndentation($tokens, $index + 1)) {
                    continue;
                }
                if ('block' === $scopes[$currentScope]['type'] || 'block_signature' === $scopes[$currentScope]['type']) {
                    $indent = \false;
                    if ($scopes[$currentScope]['is_indented_block']) {
                        $firstNonWhitespaceTokenIndex = null;
                        $nextNewlineIndex = null;
                        for ($searchIndex = $index + 1, $max = \count($tokens); $searchIndex < $max; ++$searchIndex) {
                            $searchToken = $tokens[$searchIndex];
                            if (!$searchToken->isWhitespace()) {
                                if (null === $firstNonWhitespaceTokenIndex) {
                                    $firstNonWhitespaceTokenIndex = $searchIndex;
                                }
                                continue;
                            }
                            if (Preg::match('/\\R/', $searchToken->getContent())) {
                                $nextNewlineIndex = $searchIndex;
                                break;
                            }
                        }
                        $endIndex = $scopes[$currentScope]['end_index'];
                        if (!$scopes[$currentScope]['end_index_inclusive']) {
                            ++$endIndex;
                        }
                        if (null !== $firstNonWhitespaceTokenIndex && $firstNonWhitespaceTokenIndex < $endIndex || null !== $nextNewlineIndex && $nextNewlineIndex < $endIndex) {
                            if ($tokens[$firstNonWhitespaceTokenIndex]->isGivenKind(\T_COMMENT) && $tokens[$tokens->getNextMeaningfulToken($firstNonWhitespaceTokenIndex)]->equals('}')) {
                                if ($tokens[$tokens->getPrevMeaningfulToken($firstNonWhitespaceTokenIndex)]->equals('{')) {
                                    $indent = \true;
                                } else {
                                    // or it was dedicated comment for next control loop
                                    // ^^ we need to check if there is a control group afterwards, and in that case don't make extra indent level
                                    $nextIndex = $tokens->getNextMeaningfulToken($firstNonWhitespaceTokenIndex);
                                    $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex);
                                    if (null !== $nextNextIndex && $tokens[$nextNextIndex]->isGivenKind([\T_ELSE, \T_ELSEIF])) {
                                        $indent = \true !== $this->configuration['stick_comment_to_next_continuous_control_statement'];
                                    } else {
                                        $indent = \true;
                                    }
                                }
                            } else {
                                $indent = \true;
                            }
                        }
                    }
                    $previousLineInitialIndent = $this->extractIndent($content);
                    if ($scopes[$currentScope]['skip']) {
                        $whitespaces = $previousLineInitialIndent;
                    } else {
                        $whitespaces = $scopes[$currentScope]['initial_indent'] . ($indent ? $this->whitespacesConfig->getIndent() : '');
                    }
                    $content = Preg::replace('/(\\R+)\\h*$/', '$1' . $whitespaces, $content);
                    $previousLineNewIndent = $this->extractIndent($content);
                } else {
                    $content = Preg::replace('/(\\R)' . $scopes[$currentScope]['initial_indent'] . '(\\h*)$/D', '$1' . $scopes[$currentScope]['new_indent'] . '$2', $content);
                }
                $lastIndent = $this->extractIndent($content);
                if ('' !== $previousOpenTagContent) {
                    $content = Preg::replace("/^{$previousOpenTagContent}/", '', $content);
                }
                if ('' !== $content) {
                    $tokens->ensureWhitespaceAtIndex($index, 0, $content);
                } elseif ($token->isWhitespace()) {
                    $tokens->clearAt($index);
                }
                if (null !== $nextToken && $nextToken->isComment()) {
                    $tokens[$index + 1] = new Token([$nextToken->getId(), Preg::replace('/(\\R)' . \preg_quote($previousLineInitialIndent, '/') . '(\\h*\\S+.*)/', '$1' . $previousLineNewIndent . '$2', $nextToken->getContent())]);
                }
                if ($token->isWhitespace()) {
                    continue;
                }
            }
            if ($this->isNewLineToken($tokens, $index)) {
                $lastIndent = $this->extractIndent($this->computeNewLineContent($tokens, $index));
            }
            while ($index >= $scopes[$currentScope]['end_index']) {
                \array_pop($scopes);
                if ([] === $scopes) {
                    return;
                }
                --$currentScope;
            }
            if ($token->isComment() || $token->equalsAny([';', ',', '}', [\T_OPEN_TAG], [\T_CLOSE_TAG], [CT::T_ATTRIBUTE_CLOSE]])) {
                continue;
            }
            if ('statement' !== $scopes[$currentScope]['type'] && 'block_signature' !== $scopes[$currentScope]['type']) {
                $endIndex = $this->findStatementEndIndex($tokens, $index, $scopes[$currentScope]['end_index']);
                if ($endIndex === $index) {
                    continue;
                }
                $scopes[] = ['type' => 'statement', 'skip' => \false, 'end_index' => $endIndex, 'end_index_inclusive' => \false, 'initial_indent' => $previousLineInitialIndent, 'new_indent' => $previousLineNewIndent, 'is_indented_block' => \true];
            }
        }
    }
    private function findStatementEndIndex(Tokens $tokens, int $index, int $parentScopeEndIndex) : int
    {
        $endIndex = null;
        $ifLevel = 0;
        $doWhileLevel = 0;
        for ($searchEndIndex = $index; $searchEndIndex <= $parentScopeEndIndex; ++$searchEndIndex) {
            $searchEndToken = $tokens[$searchEndIndex];
            if ($searchEndToken->isGivenKind(\T_IF) && !$tokens[$tokens->getPrevMeaningfulToken($searchEndIndex)]->isGivenKind(\T_ELSE)) {
                ++$ifLevel;
                continue;
            }
            if ($searchEndToken->isGivenKind(\T_DO)) {
                ++$doWhileLevel;
                continue;
            }
            if ($searchEndToken->equalsAny(['(', '{', [CT::T_ARRAY_SQUARE_BRACE_OPEN]])) {
                if ($searchEndToken->equals('(')) {
                    $blockType = Tokens::BLOCK_TYPE_PARENTHESIS_BRACE;
                } elseif ($searchEndToken->equals('{')) {
                    $blockType = Tokens::BLOCK_TYPE_CURLY_BRACE;
                } else {
                    $blockType = Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE;
                }
                $searchEndIndex = $tokens->findBlockEnd($blockType, $searchEndIndex);
                $searchEndToken = $tokens[$searchEndIndex];
            }
            if (!$searchEndToken->equalsAny([';', ',', '}', [\T_CLOSE_TAG]])) {
                continue;
            }
            $controlStructureContinuationIndex = $tokens->getNextMeaningfulToken($searchEndIndex);
            if ($ifLevel > 0 && null !== $controlStructureContinuationIndex && $tokens[$controlStructureContinuationIndex]->isGivenKind([\T_ELSE, \T_ELSEIF])) {
                if ($tokens[$controlStructureContinuationIndex]->isGivenKind(\T_ELSE) && !$tokens[$tokens->getNextMeaningfulToken($controlStructureContinuationIndex)]->isGivenKind(\T_IF)) {
                    --$ifLevel;
                }
                $searchEndIndex = $controlStructureContinuationIndex;
                continue;
            }
            if ($doWhileLevel > 0 && null !== $controlStructureContinuationIndex && $tokens[$controlStructureContinuationIndex]->isGivenKind(\T_WHILE)) {
                --$doWhileLevel;
                $searchEndIndex = $controlStructureContinuationIndex;
                continue;
            }
            $endIndex = $tokens->getPrevNonWhitespace($searchEndIndex);
            break;
        }
        return $endIndex ?? $tokens->getPrevMeaningfulToken($parentScopeEndIndex);
    }
    /**
     * @return array{int, bool}
     */
    private function findCaseBlockEnd(Tokens $tokens, int $index) : array
    {
        for ($max = \count($tokens); $index < $max; ++$index) {
            if ($tokens[$index]->isGivenKind(\T_SWITCH)) {
                $braceIndex = $tokens->getNextMeaningfulToken($tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextMeaningfulToken($index)));
                if ($tokens[$braceIndex]->equals(':')) {
                    $index = $this->alternativeSyntaxAnalyzer->findAlternativeSyntaxBlockEnd($tokens, $index);
                } else {
                    $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $braceIndex);
                }
                continue;
            }
            if ($tokens[$index]->equals('{')) {
                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
                continue;
            }
            if ($tokens[$index]->isGivenKind([\T_CASE, \T_DEFAULT])) {
                return [$index, \true];
            }
            if ($tokens[$index]->equalsAny(['}', [\T_ENDSWITCH]])) {
                return [$tokens->getPrevNonWhitespace($index), \false];
            }
        }
        throw new \LogicException('End of case block not found.');
    }
    private function getLineIndentationWithBracesCompatibility(Tokens $tokens, int $index, string $regularIndent) : string
    {
        if ($this->bracesFixerCompatibility && $tokens[$index]->isGivenKind(\T_OPEN_TAG) && Preg::match('/\\R/', $tokens[$index]->getContent()) && isset($tokens[$index + 1]) && $tokens[$index + 1]->isWhitespace() && Preg::match('/\\h+$/D', $tokens[$index + 1]->getContent())) {
            return Preg::replace('/.*?(\\h+)$/sD', '$1', $tokens[$index + 1]->getContent());
        }
        return $regularIndent;
    }
    /**
     * Returns whether the token at given index is the last token in a property
     * declaration before the type or the name of that property.
     */
    private function isPropertyStart(Tokens $tokens, int $index) : bool
    {
        $nextIndex = $tokens->getNextMeaningfulToken($index);
        if (null === $nextIndex || $tokens[$nextIndex]->isGivenKind(self::PROPERTY_KEYWORDS) || $tokens[$nextIndex]->isGivenKind([\T_CONST, \T_FUNCTION])) {
            return \false;
        }
        while ($tokens[$index]->isGivenKind(self::PROPERTY_KEYWORDS)) {
            if ($tokens[$index]->isGivenKind([\T_VAR, \T_PUBLIC, \T_PROTECTED, \T_PRIVATE])) {
                return \true;
            }
            $index = $tokens->getPrevMeaningfulToken($index);
        }
        return \false;
    }
    /**
     * Returns whether the token at given index is a comment whose indentation
     * can be fixed.
     *
     * Indentation of a comment is not changed when the comment is part of a
     * multi-line message whose lines are all single-line comments and at least
     * one line has meaningful content.
     */
    private function isCommentWithFixableIndentation(Tokens $tokens, int $index) : bool
    {
        if (!$tokens[$index]->isComment()) {
            return \false;
        }
        if (\strncmp($tokens[$index]->getContent(), '/*', \strlen('/*')) === 0) {
            return \true;
        }
        $indent = \preg_quote($this->whitespacesConfig->getIndent(), '~');
        if (Preg::match("~^(//|#)({$indent}.*)?\$~", $tokens[$index]->getContent())) {
            return \false;
        }
        $firstCommentIndex = $index;
        while (\true) {
            $firstCommentCandidateIndex = $this->getSiblingContinuousSingleLineComment($tokens, $firstCommentIndex, \false);
            if (null === $firstCommentCandidateIndex) {
                break;
            }
            $firstCommentIndex = $firstCommentCandidateIndex;
        }
        $lastCommentIndex = $index;
        while (\true) {
            $lastCommentCandidateIndex = $this->getSiblingContinuousSingleLineComment($tokens, $lastCommentIndex, \true);
            if (null === $lastCommentCandidateIndex) {
                break;
            }
            $lastCommentIndex = $lastCommentCandidateIndex;
        }
        if ($firstCommentIndex === $lastCommentIndex) {
            return \true;
        }
        for ($i = $firstCommentIndex + 1; $i < $lastCommentIndex; ++$i) {
            if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isComment()) {
                return \false;
            }
        }
        return \true;
    }
    private function getSiblingContinuousSingleLineComment(Tokens $tokens, int $index, bool $after) : ?int
    {
        $siblingIndex = $index;
        do {
            if ($after) {
                $siblingIndex = $tokens->getNextTokenOfKind($siblingIndex, [[\T_COMMENT]]);
            } else {
                $siblingIndex = $tokens->getPrevTokenOfKind($siblingIndex, [[\T_COMMENT]]);
            }
            if (null === $siblingIndex) {
                return null;
            }
        } while (\strncmp($tokens[$siblingIndex]->getContent(), '/*', \strlen('/*')) === 0);
        $newLines = 0;
        for ($i = \min($siblingIndex, $index) + 1, $max = \max($siblingIndex, $index); $i < $max; ++$i) {
            if ($tokens[$i]->isWhitespace() && Preg::match('/\\R/', $tokens[$i]->getContent())) {
                if (1 === $newLines || Preg::match('/\\R.*\\R/', $tokens[$i]->getContent())) {
                    return null;
                }
                ++$newLines;
            }
        }
        return $siblingIndex;
    }
}
