<?php

declare(strict_types=1);

namespace PpPhp\Core;

use ReflectionClass;
use ReflectionNamedType;
use RuntimeException;

class Container
{
    /**
     * Stored services (shared instances).
     */
    private array $instances = [];

    /**
     * Bind a concrete instance to a name.
     */
    public function set(string $id, object $instance): void
    {
        $this->instances[$id] = $instance;
    }

    /**
     * Get a service by id. If not set, try to autowire.
     */
    public function get(string $id): object
    {
        if (isset($this->instances[$id])) {
            return $this->instances[$id];
        }

        return $this->make($id);
    }

    /**
     * Autowire a class by resolving its constructor dependencies.
     */
    public function make(string $class): object
    {
        $reflection = new ReflectionClass($class);

        if (!$reflection->isInstantiable()) {
            throw new RuntimeException("Class $class is not instantiable.");
        }

        $constructor = $reflection->getConstructor();

        if ($constructor === null) {
            return new $class();
        }

        $parameters = $constructor->getParameters();
        $dependencies = [];

        foreach ($parameters as $param) {
            $type = $param->getType();
            if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) {
                $dependencies[] = $this->get($type->getName());
            } elseif ($param->isDefaultValueAvailable()) {
                $dependencies[] = $param->getDefaultValue();
            } else {
                throw new RuntimeException("Cannot resolve parameter \${$param->getName()} for $class");
            }
        }

        return $reflection->newInstanceArgs($dependencies);
    }

    /**
     * Check if a service is stored.
     */
    public function has(string $id): bool
    {
        return isset($this->instances[$id]);
    }
}