<?php

declare(strict_types=1);

namespace FriendsOfTYPO3\Rector\TYPO3\Rector\Extbase;

use Rector\Core\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use PhpParser\Node\Param;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Type\ObjectType;
use Rector\Core\NodeManipulator\ClassDependencyManipulator;
use Rector\PostRector\ValueObject\PropertyMetadata;

final class InjectMethodToConstructorInjectionRector extends AbstractRector
{
    private $classDependencyManipulator;

    public function __construct(ClassDependencyManipulator $classDependencyManipulator) {
        $this->classDependencyManipulator = $classDependencyManipulator;
    }

    public function getRuleDefinition(): RuleDefinition
    {
        return new RuleDefinition(
            '',
            [
                new CodeSample(<<<'CODE_SAMPLE'
namespace App\Service;

use \TYPO3\CMS\Core\Cache\CacheManager;

class Service
{
    private CacheManager $cacheManager;

    public function injectCacheManager(CacheManager $cacheManager): void
    {
        $this->cacheManager = $cacheManager;
    }
}
CODE_SAMPLE, <<<'CODE_SAMPLE'
namespace App\Service;

use \TYPO3\CMS\Core\Cache\CacheManager;

class Service
{
    private CacheManager $cacheManager;

    public function __construct(CacheManager $cacheManager)
    {
        $this->cacheManager = $cacheManager;
    }
}
CODE_SAMPLE
                )
            ]
        );
    }

    /**
     * List of nodes this class checks, classes that implements \PhpParser\Node
     * See beautiful map of all nodes https://github.com/rectorphp/rector/blob/master/docs/NodesOverview.md
     *
     * @return class-string[]
     */
    public function getNodeTypes(): array
    {
        return [Class_::class];
    }

    /**
     * Process Node of matched type
     */
    public function refactor(Node $node): ?Node
    {
        /** @var Class_ $node  */
        if ($this->shouldSkip($node)) {
            return null;
        }

        $injectMethods = array_filter($node->getMethods(), function ($classMethod) {
            return strncmp((string)$classMethod->name, 'inject', 6) === 0;
        });

        if ($injectMethods === []) {
            return null;
        }

        foreach ($injectMethods as $injectMethod) {
            $params = $injectMethod->getParams();
            if (empty($params)) {
                continue;
            }

            reset($params);

            /** @var Param $param */
            $param = current($params);

            if (!$param->type instanceof FullyQualified) {
                continue;
            }



            $this->classDependencyManipulator->addConstructorDependency(
                $node,
                new PropertyMetadata(
                    (string)$param->var->name,
                    new ObjectType((string)$param->type),
                    Class_::MODIFIER_PROTECTED
                )
            );

            $this->removeNodeFromStatements($node, $injectMethod);
        }

        return $node;
    }

    private function shouldSkip(Class_ $class): bool
    {
        return $class->getMethods() === [];
    }
}