<?php

/**
 * Abstract Container - Base dependency-injection container implementation.
 *
 * Provides core container functionality with extension points for plugins.
 * Can be extended but keeps zero external dependencies.
 */

declare(strict_types=1);

namespace CloudXM\SDK\Bootstrap;

use CloudXM\SDK\Contracts\ContainerInterface;
use ReflectionClass;
use ReflectionException;

abstract class AbstractContainer implements ContainerInterface
{
    /** @var array<class-string,object> */
    private array $singletons = [];

    /** @var array<class-string,callable> */
    private array $bindings = [];

    /** @var bool */
    private bool $booted = false;

    /**
     * Register a singleton binding.
     */
    public function singleton(string $abstract, callable|string|null $concrete = null): void
    {
        $this->bindings[$abstract] = $concrete ?? function (self $c) use ($abstract) {
            return $c->autowire($abstract);
        };
    }

    /**
     * Register a transient binding (new instance each time).
     */
    public function bind(string $abstract, callable|string|null $concrete = null, bool $shared = false): void
    {
        $this->bindings[$abstract] = $concrete ?? function (self $c) use ($abstract) {
            return $c->autowire($abstract);
        };
    }

    /**
     * Register an alias for a service.
     */
    public function alias(string $abstract, string $alias): void
    {
        $this->bindings[$alias] = $this->bindings[$abstract] ?? function (self $c) use ($abstract) {
            return $c->get($abstract);
        };
    }

    /**
     * Resolve a service from the container.
     */
    public function get(string $id): mixed
    {
        if (isset($this->singletons[$id])) {
            return $this->singletons[$id];
        }
        if (!isset($this->bindings[$id])) {
            // Auto-register for simple concrete classes
            $this->singleton($id);
        }
        $object = ($this->bindings[$id])($this);
        return $this->singletons[$id] = $object;
    }

    /**
     * Check if service exists.
     */
    public function has(string $id): bool
    {
        return isset($this->bindings[$id]) || isset($this->singletons[$id]);
    }

    /**
     * Boot the container.
     */
    public function boot(): void
    {
        $this->booted = true;
    }

    /**
     * Check if container is booted.
     */
    public function isBooted(): bool
    {
        return $this->booted;
    }

    /**
     * Get all registered service IDs.
     */
    public function getServiceIds(): array
    {
        return array_keys($this->bindings);
    }

    /**
     * Clear container state.
     */
    public function reset(): void
    {
        $this->singletons = [];
        $this->bindings = [];
        $this->booted = false;
    }

    /**
     * Autowire a class by resolving its constructor dependencies.
     */
    private function autowire(string $id): object
    {
        try {
            $ref = new ReflectionClass($id);
            if (!$ref->isInstantiable()) {
                throw new \RuntimeException("Class {$id} is not instantiable");
            }
            $ctor = $ref->getConstructor();
            if (!$ctor || $ctor->getNumberOfParameters() === 0) {
                return $ref->newInstance();
            }
            $deps = [];
            foreach ($ctor->getParameters() as $param) {
                $type = $param->getType();
                if (!$type || $type->isBuiltin()) {
                    throw new \RuntimeException("Cannot resolve parameter ".$param->getName()." for {$id}");
                }
                /** @var class-string $class */
                $class = $type->getName();
                $deps[] = $this->get($class);
            }
            return $ref->newInstanceArgs($deps);
        } catch (ReflectionException $e) {
            throw new \RuntimeException("Unable to autowire {$id}: ".$e->getMessage(), 0, $e);
        }
    }
}
