<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Manifest;

final class ManifestManager
{
    private ManifestRepository $repository;
    private ManifestCache $cache;
    private ManifestValidator $validator;
    private array $warnings = [];

    public function __construct(
        private readonly string $basePath,
        ?ManifestRepository $repository = null,
        ?ManifestCache $cache = null,
        ?ManifestValidator $validator = null
    ) {
        $this->repository = $repository ?? new ManifestRepository();
        $this->cache = $cache ?? new ManifestCache($this->basePath . '/storage/cache/platform-manifests.json');
        $this->validator = $validator ?? new ManifestValidator();
    }

    public function load(bool $useCache = true): ManifestCollection
    {
        if ($useCache) {
            $cached = $this->cache->load();
            if ($cached instanceof ManifestCollection) {
                $this->repository->setCollection($cached);
                return $cached;
            }
        }

        return $this->reload();
    }

    public function reload(): ManifestCollection
    {
        $collection = new ManifestCollection();
        $this->warnings = [];

        foreach ($this->manifestFiles() as $file) {
            $data = json_decode((string) file_get_contents($file), true);
            $relative = ltrim(str_replace($this->basePath, '', $file), DIRECTORY_SEPARATOR);

            if (!is_array($data)) {
                $this->warnings[] = [
                    'file' => $relative,
                    'message' => 'Invalid manifest JSON.',
                ];
                continue;
            }

            $manifest = ManifestFactory::fromArray($data, $relative);
            $errors = $this->validator->validate($manifest);

            foreach ($errors as $error) {
                $this->warnings[] = [
                    'file' => $relative,
                    'message' => $error,
                ];
            }

            if ($errors === []) {
                $collection->add($manifest);
            }
        }

        $this->repository->setCollection($collection);
        $this->cache->save($collection);

        return $collection;
    }

    public function all(): array { return $this->repository->all(); }
    public function byType(string $type): array { return $this->repository->byType($type); }
    public function modules(): array { return $this->byType('module'); }
    public function plugins(): array { return $this->byType('plugin'); }
    public function packages(): array { return $this->byType('package'); }
    public function themes(): array { return $this->byType('theme'); }

    public function find(string $type, string $id): ?ManifestRecord
    {
        return $this->repository->find($type, $id);
    }

    public function findById(string $id): ?ManifestRecord
    {
        return $this->repository->findById($id);
    }

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

    public function cache(): ManifestCache
    {
        return $this->cache;
    }

    public function collection(): ManifestCollection
    {
        return $this->repository->collection();
    }

    private function manifestFiles(): array
    {
        $files = [];

        if (!is_dir($this->basePath)) {
            return $files;
        }

        $skip = [
            DIRECTORY_SEPARATOR . '.git' . DIRECTORY_SEPARATOR,
            DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR,
            DIRECTORY_SEPARATOR . 'node_modules' . DIRECTORY_SEPARATOR,
            DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR,
        ];

        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($this->basePath, \FilesystemIterator::SKIP_DOTS)
        );

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

            $path = $file->getPathname();

            foreach ($skip as $fragment) {
                if (str_contains($path, $fragment)) {
                    continue 2;
                }
            }

            if ($file->getFilename() === 'manifest.json') {
                $files[] = $path;
            }
        }

        sort($files);

        return $files;
    }
}
