<?php
/**
 * Dependency Injection Container
 *
 * @package AIProductContentImageGenerator\Core
 */

namespace AIProductContentImageGenerator\Core;

if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly
}

/**
 * Simple Dependency Injection Container
 */
class Container {
    /**
     * Registered services
     *
     * @var array
     */
    private $services = array();

    /**
     * Service instances
     *
     * @var array
     */
    private $instances = array();

    /**
     * Register a service
     *
     * @param string   $name     Service name
     * @param callable $callback Service factory callback
     * @param bool     $shared   Whether service should be shared (singleton)
     */
    public function register(string $name, callable $callback, bool $shared = true): void {
        $this->services[$name] = array(
            'callback' => $callback,
            'shared' => $shared
        );
    }

    /**
     * Get a service instance
     *
     * @param string $name Service name
     * @return mixed
     * @throws \Exception If service not found
     */
    public function get(string $name) {
        if (!isset($this->services[$name])) {
            throw new \Exception('Service not found in container: ' . esc_html($name));
        }

        $service = $this->services[$name];

        // Return existing instance if shared
        if ($service['shared'] && isset($this->instances[$name])) {
            return $this->instances[$name];
        }

        // Create new instance
        $instance = call_user_func($service['callback']);

        // Store instance if shared
        if ($service['shared']) {
            $this->instances[$name] = $instance;
        }

        return $instance;
    }

    /**
     * Check if service is registered
     *
     * @param string $name Service name
     * @return bool
     */
    public function has(string $name): bool {
        return isset($this->services[$name]);
    }

    /**
     * Remove a service
     *
     * @param string $name Service name
     */
    public function remove(string $name): void {
        unset($this->services[$name], $this->instances[$name]);
    }

    /**
     * Get all registered service names
     *
     * @return array
     */
    public function getServiceNames(): array {
        return array_keys($this->services);
    }

    /**
     * Clear all services and instances
     */
    public function clear(): void {
        $this->services = array();
        $this->instances = array();
    }

    /**
     * Register multiple services at once
     *
     * @param array $services Array of service definitions
     */
    public function registerMultiple(array $services): void {
        foreach ($services as $name => $definition) {
            if (is_callable($definition)) {
                $this->register($name, $definition);
            } elseif (is_array($definition) && isset($definition['callback'])) {
                $this->register(
                    $name,
                    $definition['callback'],
                    $definition['shared'] ?? true
                );
            }
        }
    }

    /**
     * Create a factory service (non-shared)
     *
     * @param string   $name     Service name
     * @param callable $callback Service factory callback
     */
    public function factory(string $name, callable $callback): void {
        $this->register($name, $callback, false);
    }

    /**
     * Create a singleton service (shared)
     *
     * @param string   $name     Service name
     * @param callable $callback Service factory callback
     */
    public function singleton(string $name, callable $callback): void {
        $this->register($name, $callback, true);
    }

    /**
     * Bind a value directly to the container
     *
     * @param string $name  Service name
     * @param mixed  $value Value to bind
     */
    public function bind(string $name, $value): void {
        $this->register($name, function() use ($value) {
            return $value;
        });
    }

    /**
     * Resolve dependencies and create instance
     *
     * @param string $className Class name to resolve
     * @return object
     * @throws \Exception If class cannot be resolved
     */
    public function resolve(string $className) {
        if (!class_exists($className)) {
            throw new \Exception('Class not found: ' . esc_html($className));
        }

        $reflection = new \ReflectionClass($className);
        $constructor = $reflection->getConstructor();

        if (!$constructor) {
            return new $className();
        }

        $parameters = $constructor->getParameters();
        $dependencies = array();

        foreach ($parameters as $parameter) {
            $type = $parameter->getType();
            
            if (!$type || $type->isBuiltin()) {
                if ($parameter->isDefaultValueAvailable()) {
                    $dependencies[] = $parameter->getDefaultValue();
                } else {
                    throw new \Exception(sprintf("Cannot resolve parameter '%s' for class '%s'.", esc_html($parameter->getName()), esc_html($className)));
                }
            } else {
                $dependencyClass = $type->getName();
                $dependencies[] = $this->resolve($dependencyClass);
            }
        }

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