<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Bootstrap;

final class PlatformClassLoader
{
    private string $basePath;

    /** @var array<string, string> */
    private array $classMap = [];

    /** @var string[] */
    private array $roots = [];

    private bool $registered = false;

    public function __construct(string $basePath, array $roots = [])
    {
        $this->basePath = rtrim($basePath, DIRECTORY_SEPARATOR);
        $this->roots = $roots !== [] ? $roots : [
            'core',
            'modules',
            'packages',
            'plugins',
        ];
    }

    public static function register(string $basePath, bool $useCache = true): self
    {
        $loader = new self($basePath);
        $loader->boot($useCache);

        return $loader;
    }

    public function boot(bool $useCache = true): void
    {
        if ($useCache && $this->loadCache()) {
            $this->registerAutoload();
            return;
        }

        $this->buildClassMap();
        $this->saveCache();
        $this->registerAutoload();
    }

    public function registerAutoload(): void
    {
        if ($this->registered) {
            return;
        }

        spl_autoload_register([$this, 'autoload'], prepend: true);
        $this->registered = true;
    }

    public function autoload(string $class): bool
    {
        if (isset($this->classMap[$class]) && is_file($this->classMap[$class])) {
            require_once $this->classMap[$class];
            return true;
        }

        $path = $this->resolvePsr4Path($class);

        if ($path !== null && is_file($path)) {
            require_once $path;
            $this->classMap[$class] = $path;
            return true;
        }

        return false;
    }

    public function buildClassMap(): void
    {
        $this->classMap = [];

        foreach ($this->roots as $root) {
            $path = $this->basePath . DIRECTORY_SEPARATOR . $root;

            if (is_dir($path)) {
                $this->scanDirectory($path);
            }
        }

        ksort($this->classMap);
    }

    private function scanDirectory(string $directory): void
    {
        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS)
        );

        foreach ($iterator as $file) {
            if (!$file instanceof \SplFileInfo || !$file->isFile() || strtolower($file->getExtension()) !== 'php') {
                continue;
            }

            $class = $this->extractClassName($file->getPathname());

            if ($class !== null) {
                $this->classMap[$class] = $file->getPathname();
            }
        }
    }

    private function extractClassName(string $file): ?string
    {
        $contents = (string) file_get_contents($file);

        $namespace = '';
        if (preg_match('/^\s*namespace\s+([^;]+);/m', $contents, $match)) {
            $namespace = trim($match[1]);
        }

        if (!preg_match('/^\s*(?:final\s+|abstract\s+)?(?:class|interface|trait|enum)\s+([A-Za-z_][A-Za-z0-9_]*)/m', $contents, $match)) {
            return null;
        }

        return $namespace !== '' ? $namespace . '\\' . $match[1] : $match[1];
    }

    private function resolvePsr4Path(string $class): ?string
    {
        $prefix = 'PpPhp\\';

        if (!str_starts_with($class, $prefix)) {
            return null;
        }

        $relative = substr($class, strlen($prefix));
        $relative = str_replace('\\', DIRECTORY_SEPARATOR, $relative) . '.php';

        $candidates = [
            $this->basePath . DIRECTORY_SEPARATOR . $relative,
            $this->basePath . DIRECTORY_SEPARATOR . 'core' . DIRECTORY_SEPARATOR . substr($relative, strlen('Core' . DIRECTORY_SEPARATOR)),
        ];

        foreach ($candidates as $candidate) {
            if (is_file($candidate)) {
                return $candidate;
            }
        }

        return null;
    }

    public function loadCache(): bool
    {
        $file = $this->cacheFile();

        if (!is_file($file)) {
            return false;
        }

        $data = json_decode((string) file_get_contents($file), true);

        if (!is_array($data) || !is_array($data['classMap'] ?? null)) {
            return false;
        }

        $this->classMap = $data['classMap'];

        return true;
    }

    public function saveCache(): void
    {
        $file = $this->cacheFile();
        $dir = dirname($file);

        if (!is_dir($dir)) {
            mkdir($dir, 0775, true);
        }

        file_put_contents($file, json_encode([
            'generatedAt' => date('c'),
            'count' => count($this->classMap),
            'classMap' => $this->classMap,
        ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
    }

    public function clearCache(): void
    {
        $file = $this->cacheFile();

        if (is_file($file)) {
            unlink($file);
        }
    }

    public function classMap(): array
    {
        return $this->classMap;
    }

    public function count(): int
    {
        return count($this->classMap);
    }

    public function cacheFile(): string
    {
        return $this->basePath . '/storage/cache/platform-classmap.json';
    }
}
