<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\DI\Reflection;

use ReflectionClass;
use ReflectionNamedType;
use ReflectionParameter;
use PpPhp\Core\Platform\DI\Exceptions\ResolutionException;

final class ReflectionService
{
    public function __construct(private readonly ReflectionCache $cache = new ReflectionCache()) {}

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

    public function inspect(string $className): ClassMetadata
    {
        if ($this->cache->has($className)) {
            return $this->cache->get($className);
        }

        if (!class_exists($className)) {
            throw new ResolutionException('Class does not exist: ' . $className);
        }

        $reflection = new ReflectionClass($className);
        $constructor = null;

        if ($reflection->getConstructor()) {
            $parameters = array_map(fn (ReflectionParameter $p): ParameterMetadata => $this->parameter($p), $reflection->getConstructor()->getParameters());
            $constructor = new ConstructorMetadata($parameters);
        }

        $attributes = array_map(static fn ($a): string => $a->getName(), $reflection->getAttributes());

        $metadata = new ClassMetadata(
            $className,
            $reflection->isInstantiable(),
            $constructor,
            array_values($reflection->getInterfaceNames()),
            $attributes
        );

        $this->cache->set($metadata);

        return $metadata;
    }

    private function parameter(ReflectionParameter $parameter): ParameterMetadata
    {
        $type = $parameter->getType();
        $name = null;
        $builtin = false;

        if ($type instanceof ReflectionNamedType) {
            $name = $type->getName();
            $builtin = $type->isBuiltin();
        }

        return new ParameterMetadata(
            $parameter->getName(),
            $name,
            $parameter->allowsNull(),
            $parameter->isDefaultValueAvailable(),
            $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null,
            $builtin
        );
    }
}
