<?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\Unit\NodeVisitor;

use PHPUnit\Framework\TestCase;
use RegexParser\Node\AlternationNode;
use RegexParser\Node\AnchorNode;
use RegexParser\Node\AssertionNode;
use RegexParser\Node\BackrefNode;
use RegexParser\Node\CalloutNode;
use RegexParser\Node\CharClassNode;
use RegexParser\Node\CharLiteralNode;
use RegexParser\Node\CharLiteralType;
use RegexParser\Node\CharTypeNode;
use RegexParser\Node\ClassOperationNode;
use RegexParser\Node\ClassOperationType;
use RegexParser\Node\CommentNode;
use RegexParser\Node\ConditionalNode;
use RegexParser\Node\ControlCharNode;
use RegexParser\Node\DefineNode;
use RegexParser\Node\DotNode;
use RegexParser\Node\GroupNode;
use RegexParser\Node\GroupType;
use RegexParser\Node\KeepNode;
use RegexParser\Node\LimitMatchNode;
use RegexParser\Node\LiteralNode;
use RegexParser\Node\PcreVerbNode;
use RegexParser\Node\PosixClassNode;
use RegexParser\Node\QuantifierNode;
use RegexParser\Node\QuantifierType;
use RegexParser\Node\RangeNode;
use RegexParser\Node\RegexNode;
use RegexParser\Node\ScriptRunNode;
use RegexParser\Node\SequenceNode;
use RegexParser\Node\SubroutineNode;
use RegexParser\Node\UnicodeNode;
use RegexParser\Node\UnicodePropNode;
use RegexParser\Node\VersionConditionNode;
use RegexParser\NodeVisitor\AbstractNodeVisitor;

/**
 * Concrete implementation of AbstractNodeVisitor for testing purposes.
 *
 * @template-extends AbstractNodeVisitor<string>
 */
class TestNodeVisitor extends AbstractNodeVisitor
{
    protected function defaultReturn(): string
    {
        return 'default';
    }
}

final class AbstractNodeVisitorTest extends TestCase
{
    private TestNodeVisitor $visitor;

    protected function setUp(): void
    {
        $this->visitor = new TestNodeVisitor();
    }

    public function test_visit_regex(): void
    {
        $node = new RegexNode(new SequenceNode([], 0, 0), '', '/', 0, 0);
        $result = $this->visitor->visitRegex($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_alternation(): void
    {
        $node = new AlternationNode([], 0, 0);
        $result = $this->visitor->visitAlternation($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_sequence(): void
    {
        $node = new SequenceNode([], 0, 0);
        $result = $this->visitor->visitSequence($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_group(): void
    {
        $node = new GroupNode(new SequenceNode([], 0, 0), GroupType::T_GROUP_CAPTURING, null, null, 0, 0);
        $result = $this->visitor->visitGroup($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_quantifier(): void
    {
        $node = new QuantifierNode(new LiteralNode('a', 0, 0), '*', QuantifierType::T_GREEDY, 0, 0);
        $result = $this->visitor->visitQuantifier($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_literal(): void
    {
        $node = new LiteralNode('a', 0, 0);
        $result = $this->visitor->visitLiteral($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_char_literal(): void
    {
        $node = new CharLiteralNode('a', 97, CharLiteralType::UNICODE, 0, 0);
        $result = $this->visitor->visitCharLiteral($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_char_type(): void
    {
        $node = new CharTypeNode('d', 0, 0);
        $result = $this->visitor->visitCharType($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_unicode(): void
    {
        $node = new UnicodeNode('u', 0, 0);
        $result = $this->visitor->visitUnicode($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_dot(): void
    {
        $node = new DotNode(0, 0);
        $result = $this->visitor->visitDot($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_anchor(): void
    {
        $node = new AnchorNode('^', 0, 0);
        $result = $this->visitor->visitAnchor($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_assertion(): void
    {
        $node = new AssertionNode('b', 0, 0);
        $result = $this->visitor->visitAssertion($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_keep(): void
    {
        $node = new KeepNode(0, 0);
        $result = $this->visitor->visitKeep($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_char_class(): void
    {
        $node = new CharClassNode(new LiteralNode('a', 0, 0), false, 0, 0);
        $result = $this->visitor->visitCharClass($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_range(): void
    {
        $node = new RangeNode(new LiteralNode('a', 0, 0), new LiteralNode('z', 0, 0), 0, 0);
        $result = $this->visitor->visitRange($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_backref(): void
    {
        $node = new BackrefNode('1', 0, 0);
        $result = $this->visitor->visitBackref($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_class_operation(): void
    {
        $node = new ClassOperationNode(
            ClassOperationType::INTERSECTION,
            new CharClassNode(new LiteralNode('a', 0, 0), false, 0, 0),
            new CharClassNode(new LiteralNode('b', 0, 0), false, 0, 0),
            0,
            0,
        );
        $result = $this->visitor->visitClassOperation($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_control_char(): void
    {
        $node = new ControlCharNode('M', 13, 0, 0);
        $result = $this->visitor->visitControlChar($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_script_run(): void
    {
        $node = new ScriptRunNode('Latin', 0, 0);
        $result = $this->visitor->visitScriptRun($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_version_condition(): void
    {
        $node = new VersionConditionNode('>=', '10.0', 0, 0);
        $result = $this->visitor->visitVersionCondition($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_unicode_prop(): void
    {
        $node = new UnicodePropNode('L', 0, 0);
        $result = $this->visitor->visitUnicodeProp($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_posix_class(): void
    {
        $node = new PosixClassNode('alnum', 0, 0);
        $result = $this->visitor->visitPosixClass($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_comment(): void
    {
        $node = new CommentNode('test', 0, 0);
        $result = $this->visitor->visitComment($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_conditional(): void
    {
        $node = new ConditionalNode(new LiteralNode('1', 0, 0), new SequenceNode([], 0, 0), new SequenceNode([], 0, 0), 0, 0);
        $result = $this->visitor->visitConditional($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_subroutine(): void
    {
        $node = new SubroutineNode('1', '', 0, 0);
        $result = $this->visitor->visitSubroutine($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_pcre_verb(): void
    {
        $node = new PcreVerbNode('FAIL', 0, 0);
        $result = $this->visitor->visitPcreVerb($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_define(): void
    {
        $node = new DefineNode(new SequenceNode([], 0, 0), 0, 0);
        $result = $this->visitor->visitDefine($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_limit_match(): void
    {
        $node = new LimitMatchNode(1, 0, 0);
        $result = $this->visitor->visitLimitMatch($node);
        $this->assertSame('default', $result);
    }

    public function test_visit_callout(): void
    {
        $node = new CalloutNode(1, false, 0, 0);
        $result = $this->visitor->visitCallout($node);
        $this->assertSame('default', $result);
    }

    public function test_default_return(): void
    {
        // Test the protected defaultReturn method through reflection
        $reflection = new \ReflectionClass($this->visitor);
        $method = $reflection->getMethod('defaultReturn');
        $result = $method->invoke($this->visitor);
        $this->assertSame('default', $result);
    }
}
