<?php

/*
 * This file is part of the phpstan-magento package.
 *
 * (c) bitExpert AG
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
declare(strict_types=1);

namespace bitExpert\PHPStan\Magento\Autoload;

use bitExpert\PHPStan\Magento\Autoload\DataProvider\ClassLoaderProvider;
use PHPStan\Cache\Cache;

class FactoryAutoloader implements Autoloader
{
    /**
     * @var Cache
     */
    private $cache;
    /**
     * @var ClassLoaderProvider
     */
    private $classLoaderProvider;

    /**
     * FactoryAutoloader constructor.
     *
     * @param Cache $cache
     * @param ClassLoaderProvider $classLoaderProvider
     */
    public function __construct(Cache $cache, ClassLoaderProvider $classLoaderProvider)
    {
        $this->cache = $cache;
        $this->classLoaderProvider = $classLoaderProvider;
    }

    public function autoload(string $class): void
    {
        if (preg_match('#Factory$#', $class) !== 1) {
            return;
        }

        // fix for PHPStan 1.7.5 and later: Classes generated by autoloaders are supposed to "win" against
        // local classes in your project. We need to check first if classes exists locally before generating them!
        $pathToLocalClass = $this->classLoaderProvider->findFile($class);
        if ($pathToLocalClass === false) {
            $pathToLocalClass = $this->cache->load($class, '');
            if ($pathToLocalClass === null) {
                $this->cache->save($class, '', $this->getFileContents($class));
                $pathToLocalClass = $this->cache->load($class, '');
            }
        }

        require_once($pathToLocalClass);
    }

    /**
     * Generate the factory file content as Magento would.
     *
     * @param string $class
     * @return string
     */
    protected function getFileContents(string $class): string
    {
        $namespace = explode('\\', ltrim($class, '\\'));
        /** @var string $factoryClassname */
        $factoryClassname = array_pop($namespace);
        $originalClassname = (string) preg_replace('#Factory$#', '', $factoryClassname);
        $namespace = implode('\\', $namespace);

        $template = "<?php\n";

        if ($namespace !== '') {
            $template .= "namespace {NAMESPACE};\n\n";
        }

        $template .= "/**\n";
        $template .= " * Factory class for @see \{NAMESPACE}\{CLASSNAME}\n";
        $template .= " */\n";
        $template .= "class {FACTORY_CLASSNAME}\n";
        $template .= "{\n";
        $template .= "    /**\n";
        $template .= "     * Create class instance with specified parameters\n";
        $template .= "     *\n";
        $template .= "     * @param array \$data\n";
        $template .= "     * @return {CLASSNAME}\n";
        $template .= "     */\n";
        $template .= "    public function create(array \$data = array()) {}\n";
        $template .= "}\n";

        return str_replace(
            [
                '{NAMESPACE}',
                '{CLASSNAME}',
                '{FACTORY_CLASSNAME}'
            ],
            [
                $namespace,
                $originalClassname,
                $factoryClassname
            ],
            $template
        );
    }

    public function register(): void
    {
        \spl_autoload_register([$this, 'autoload'], true, false);
    }

    public function unregister(): void
    {
        spl_autoload_unregister([$this, 'autoload']);
    }
}
