<?php

declare(strict_types=1);

/*
 * This file is part of the RegexParser package.
 *
 * (c) Younes ENNAJI <younes.ennaji.pro@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace RegexParser\Tests\TestUtils;

use RegexParser\Parser;
use RegexParser\Token;
use RegexParser\TokenStream;
use RegexParser\TokenType;

/**
 * Accessor class to expose and manipulate private methods/properties of the Parser for unit testing.
 *
 * NOTE: After the TokenStream refactoring, this accessor works differently.
 * The Parser now uses a TokenStream internally instead of a tokens array and position property.
 */
final readonly class ParserAccessor
{
    /**
     * @var \ReflectionClass<Parser>
     */
    private \ReflectionClass $reflection;

    public function __construct(private Parser $parser)
    {
        $this->reflection = new \ReflectionClass($this->parser);
    }

    /**
     * Calls a private method on the Parser instance.
     *
     * @param array<mixed> $args
     */
    public function callPrivateMethod(string $methodName, array $args = []): mixed
    {
        $method = $this->reflection->getMethod($methodName);

        return $method->invoke($this->parser, ...$args);
    }

    /**
     * Sets the internal TokenStream by creating one from an array of tokens.
     *
     * @param array<string|Token> $tokens
     */
    public function setTokens(array $tokens): void
    {
        $processedTokens = [];
        $pos = 0;
        foreach ($tokens as $token) {
            if ($token instanceof Token) {
                $processedTokens[] = $token;
                $pos += \strlen($token->value);
            } else {
                $processedTokens[] = $this->createToken(TokenType::T_LITERAL, $token, $pos);
                $pos += \strlen($token);
            }
        }
        $processedTokens[] = $this->createToken(TokenType::T_EOF, '', $pos);

        $stream = new TokenStream($processedTokens, '');

        $property = $this->reflection->getProperty('stream');
        $property->setValue($this->parser, $stream);
    }

    /**
     * Sets the current position of the parser in the token stream.
     */
    public function setPosition(int $position): void
    {
        $property = $this->reflection->getProperty('stream');
        $stream = $property->getValue($this->parser);

        if ($stream instanceof TokenStream) {
            $stream->setPosition($position);
        }
    }

    /**
     * Gets the current position of the parser in the token stream.
     */
    public function getPosition(): int
    {
        $property = $this->reflection->getProperty('stream');
        $stream = $property->getValue($this->parser);

        if ($stream instanceof TokenStream) {
            return $stream->getPosition();
        }

        return 0;
    }

    /**
     * Utility method to create a Token instance.
     */
    public function createToken(TokenType $type, string $value, int $position): Token
    {
        return new Token($type, $value, $position);
    }

    /**
     * Wrapper for private advance() method.
     */
    public function advance(): void
    {
        $this->callPrivateMethod('advance');
    }

    /**
     * Wrapper for private current() method.
     */
    public function current(): Token
    {
        $result = $this->callPrivateMethod('current');
        \assert($result instanceof Token);

        return $result;
    }
}
