<commit_before><?php declare(strict_types=1);

namespace Symplify\BetterPhpDocParser\Printer;

use Nette\Utils\Strings;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use PHPStan\PhpDocParser\Lexer\Lexer;
use Symplify\BetterPhpDocParser\PhpDocNodeInfo;
use Symplify\BetterPhpDocParser\PhpDocParser\Ast\Type\FormatPreservingUnionTypeNode;
use Symplify\BetterPhpDocParser\PhpDocParser\PhpDocInfo;
use Symplify\BetterPhpDocParser\PhpDocParser\Storage\NodeWithPositionsObjectStorage;

final class PhpDocInfoPrinter
{
    /**
     * @var NodeWithPositionsObjectStorage|PhpDocNodeInfo[]
     */
    private $nodeWithPositionsObjectStorage;

    /**
     * @var mixed[]
     */
    private $tokens = [];

    /**
     * @var int
     */
    private $tokenCount;

    /**
     * @var int
     */
    private $currentTokenPosition;

    /**
     * @var PhpDocNode
     */
    private $phpDocNode;

    /**
     * @var PhpDocNode
     */
    private $originalPhpDocNode;

    /**
     * @var PhpDocNodeInfo[]
     */
    private $removedNodePositions = [];

    /**
     * @var OriginalSpacingRestorer
     */
    private $originalSpacingRestorer;

    public function __construct(
        NodeWithPositionsObjectStorage $nodeWithPositionsObjectStorage,
        OriginalSpacingRestorer $originalSpacingRestorer
    ) {
        $this->nodeWithPositionsObjectStorage = $nodeWithPositionsObjectStorage;
        $this->originalSpacingRestorer = $originalSpacingRestorer;
    }

    /**
     * As in php-parser
     *
     * ref: https://github.com/nikic/PHP-Parser/issues/487#issuecomment-375986259
     * - Tokens[node.startPos .. subnode1.startPos]
     * - Print(subnode1)
     * - Tokens[subnode1.endPos .. subnode2.startPos]
     * - Print(subnode2)
     * - Tokens[subnode2.endPos .. node.endPos]
     */
    public function printFormatPreserving(PhpDocInfo $phpDocInfo): string
    {
        $this->phpDocNode = $phpDocInfo->getPhpDocNode();
        $this->originalPhpDocNode = $phpDocInfo->getOriginalPhpDocNode();
        $this->tokens = $phpDocInfo->getTokens();
        $this->tokenCount = count($phpDocInfo->getTokens());

        $this->currentTokenPosition = 0;
        $this->removedNodePositions = [];

        return $this->printPhpDocNode($this->phpDocNode);
    }

    private function printPhpDocNode(PhpDocNode $phpDocNode): string
    {
        // no nodes were, so empty doc
        if ($this->isPhpDocNodeEmpty($phpDocNode)) {
            return '';
        }

        $this->currentTokenPosition = 0;

        $output = '';

        // node output
        $nodeCount = count($phpDocNode->children);
        foreach ($phpDocNode->children as $i => $child) {
            $output .= $this->printNode($child, null, $i + 1, $nodeCount);
        }

        return $this->printEnd($output);
    }

    private function printNode(
        Node $node,
        ?PhpDocNodeInfo $phpDocNodeInfo = null,
        int $i = 0,
        int $nodeCount = 0
    ): string {
        $output = '';

        // tokens before
        if (isset($this->nodeWithPositionsObjectStorage[$node])) {
            $phpDocNodeInfo = $this->nodeWithPositionsObjectStorage[$node];

            $isLastToken = $nodeCount === $i;
            $output = $this->addTokensFromTo(
                $output,
                $this->currentTokenPosition,
                $phpDocNodeInfo->getStart(),
                $isLastToken
            );
            $this->currentTokenPosition = $phpDocNodeInfo->getEnd();
        }

        if ($node instanceof PhpDocTagNode) {
            return $this->printPhpDocTagNode($node, $phpDocNodeInfo, $output);
        }

        // @todo for the rest of nodes as well
        if ($node instanceof ParamTagValueNode || $node instanceof PropertyTagValueNode) {
            return $this->originalSpacingRestorer->restoreInOutputWithTokensAndPhpDocNodeInfo(
                (string) $node,
                $this->tokens,
                $phpDocNodeInfo
            );
        }

        return $output . (string) $node;
    }

    private function printPhpDocTagNode(
        PhpDocTagNode $phpDocTagNode,
        PhpDocNodeInfo $phpDocNodeInfo,
        string $output
    ): string {
        if ($phpDocTagNode->value instanceof ParamTagValueNode || $phpDocTagNode->value instanceof ReturnTagValueNode || $phpDocTagNode->value instanceof VarTagValueNode) {
            if ($phpDocTagNode->value->type instanceof UnionTypeNode) {
                // @todo temp workaround
                $nodeValueType = $phpDocTagNode->value->type;
                /** @var UnionTypeNode $nodeValueType */
                $phpDocTagNode->value->type = new FormatPreservingUnionTypeNode($nodeValueType->types);
            }
        }

        $output .= $phpDocTagNode->name;<commit_msg>fix format preserving for other nodes as well<commit_after><?php declare(strict_types=1);

namespace Symplify\BetterPhpDocParser\Printer;

use Nette\Utils\Strings;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use PHPStan\PhpDocParser\Lexer\Lexer;
use Symplify\BetterPhpDocParser\PhpDocNodeInfo;
use Symplify\BetterPhpDocParser\PhpDocParser\Ast\Type\FormatPreservingUnionTypeNode;
use Symplify\BetterPhpDocParser\PhpDocParser\PhpDocInfo;
use Symplify\BetterPhpDocParser\PhpDocParser\Storage\NodeWithPositionsObjectStorage;

final class PhpDocInfoPrinter
{
    /**
     * @var NodeWithPositionsObjectStorage|PhpDocNodeInfo[]
     */
    private $nodeWithPositionsObjectStorage;

    /**
     * @var mixed[]
     */
    private $tokens = [];

    /**
     * @var int
     */
    private $tokenCount;

    /**
     * @var int
     */
    private $currentTokenPosition;

    /**
     * @var PhpDocNode
     */
    private $phpDocNode;

    /**
     * @var PhpDocNode
     */
    private $originalPhpDocNode;

    /**
     * @var PhpDocNodeInfo[]
     */
    private $removedNodePositions = [];

    /**
     * @var OriginalSpacingRestorer
     */
    private $originalSpacingRestorer;

    public function __construct(
        NodeWithPositionsObjectStorage $nodeWithPositionsObjectStorage,
        OriginalSpacingRestorer $originalSpacingRestorer
    ) {
        $this->nodeWithPositionsObjectStorage = $nodeWithPositionsObjectStorage;
        $this->originalSpacingRestorer = $originalSpacingRestorer;
    }

    /**
     * As in php-parser
     *
     * ref: https://github.com/nikic/PHP-Parser/issues/487#issuecomment-375986259
     * - Tokens[node.startPos .. subnode1.startPos]
     * - Print(subnode1)
     * - Tokens[subnode1.endPos .. subnode2.startPos]
     * - Print(subnode2)
     * - Tokens[subnode2.endPos .. node.endPos]
     */
    public function printFormatPreserving(PhpDocInfo $phpDocInfo): string
    {
        $this->phpDocNode = $phpDocInfo->getPhpDocNode();
        $this->originalPhpDocNode = $phpDocInfo->getOriginalPhpDocNode();
        $this->tokens = $phpDocInfo->getTokens();
        $this->tokenCount = count($phpDocInfo->getTokens());

        $this->currentTokenPosition = 0;
        $this->removedNodePositions = [];

        return $this->printPhpDocNode($this->phpDocNode);
    }

    private function printPhpDocNode(PhpDocNode $phpDocNode): string
    {
        // no nodes were, so empty doc
        if ($this->isPhpDocNodeEmpty($phpDocNode)) {
            return '';
        }

        $this->currentTokenPosition = 0;

        $output = '';

        // node output
        $nodeCount = count($phpDocNode->children);
        foreach ($phpDocNode->children as $i => $child) {
            $output .= $this->printNode($child, null, $i + 1, $nodeCount);
        }

        return $this->printEnd($output);
    }

    private function printNode(
        Node $node,
        ?PhpDocNodeInfo $phpDocNodeInfo = null,
        int $i = 0,
        int $nodeCount = 0
    ): string {
        $output = '';

        // tokens before
        if (isset($this->nodeWithPositionsObjectStorage[$node])) {
            $phpDocNodeInfo = $this->nodeWithPositionsObjectStorage[$node];

            $isLastToken = ($nodeCount === $i);

            $output = $this->addTokensFromTo(
                $output,
                $this->currentTokenPosition,
                $phpDocNodeInfo->getStart(),
                $isLastToken
            );

            $this->currentTokenPosition = $phpDocNodeInfo->getEnd();
        }

        if ($node instanceof PhpDocTagNode) {
            return $this->printPhpDocTagNode($node, $phpDocNodeInfo, $output);
        }

        if (! $node instanceof PhpDocTextNode && ! $node instanceof GenericTagValueNode) {
            return $this->originalSpacingRestorer->restoreInOutputWithTokensAndPhpDocNodeInfo(
                (string) $node,
                $this->tokens,
                $phpDocNodeInfo
            );
        }

        return $output . (string) $node;
    }

    private function printPhpDocTagNode(
        PhpDocTagNode $phpDocTagNode,
        PhpDocNodeInfo $phpDocNodeInfo,
        string $output
    ): string {
        if ($phpDocTagNode->value instanceof ParamTagValueNode || $phpDocTagNode->value instanceof ReturnTagValueNode || $phpDocTagNode->value instanceof VarTagValueNode) {
            if ($phpDocTagNode->value->type instanceof UnionTypeNode) {
                // @todo temp workaround
                $nodeValueType = $phpDocTagNode->value->type;
                /** @var UnionTypeNode $nodeValueType */
                $phpDocTagNode->value->type = new FormatPreservingUnionTypeNode($nodeValueType->types);
            }
        }

        $output .= $phpDocTagNode->name;
{'fraction_non_alphanumeric': 0.11378051124351336, 'fraction_numerical': 0.0046127234287910825, 'mean_word_length': 2.6022845275181723, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 89, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3521027', 'n_tokens_mistral': 3284, 'n_tokens_neox': 3126, 'n_words': 496}