<?php

namespace App\Framework\DI;

use App\Framework\DI\Exceptions\ParameterResolutionException;
use App\Framework\Reflection\ReflectionProvider;
use ReflectionNamedType;
use ReflectionParameter;

/**
 * Löst Constructor-Abhängigkeiten auf
 */
final class DependencyResolver
{
    private array $constructorCache = [];

    public function __construct(
        private readonly ReflectionProvider $reflectionProvider,
        private readonly Container $container
    ) {}

    public function resolveDependencies(string $class): array
    {
        $reflection = $this->reflectionProvider->getClass($class);
        $constructor = $reflection->getConstructor();

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

        if (isset($this->constructorCache[$class])) {
            return $this->resolveFromCache($class);
        }

        $dependencies = [];
        $cacheInfo = [];

        foreach ($constructor->getParameters() as $param) {
            $dependency = $this->resolveParameter($param, $class);
            $dependencies[] = $dependency['value'];
            $cacheInfo[] = $dependency['cache'];
        }

        // Nur cacheable Parameter cachen
        if (!in_array(false, array_column($cacheInfo, 'cacheable'), true)) {
            $this->constructorCache[$class] = $cacheInfo;
        }

        return $dependencies;
    }

    private function resolveParameter(ReflectionParameter $param, string $class): array
    {
        $type = $param->getType();

        if ($param->isDefaultValueAvailable()) {
            return [
                'value' => $param->getDefaultValue(),
                'cache' => ['type' => 'default', 'value' => $param->getDefaultValue(), 'cacheable' => true]
            ];
        }

        if ($type instanceof ReflectionNamedType && $type->allowsNull()) {
            return [
                'value' => null,
                'cache' => ['type' => 'null', 'cacheable' => true]
            ];
        }

        if (!$type || $type->isBuiltin()) {
            throw new ParameterResolutionException(
                paramName: $param->getName(),
                class: $class,
                dependencyChain: [] // Wird vom Container gesetzt
            );
        }

        $typeName = $type->getName();
        return [
            'value' => $this->container->get($typeName),
            'cache' => ['type' => 'dependency', 'class' => $typeName, 'cacheable' => false]
        ];
    }

    private function resolveFromCache(string $class): array
    {
        $cacheInfo = $this->constructorCache[$class];
        $dependencies = [];

        foreach ($cacheInfo as $info) {
            $dependencies[] = match ($info['type']) {
                'default' => $info['value'],
                'null' => null,
                'dependency' => $this->container->get($info['class']),
            };
        }

        return $dependencies;
    }

    public function clearCache(string $class): void
    {
        unset($this->constructorCache[$class]);
    }

    public function flushCache(): void
    {
        $this->constructorCache = [];
    }
}
