<?php

namespace FleekDash\Integrations\Core;

defined('ABSPATH') || exit;

/**
 * Dependency Injection Container for Integrations
 *
 * Manages integration lifecycle, dependencies, and lazy loading
 */
class Container
{
    private static ?Container $instance = null;
    private array $bindings = [];
    private array $instances = [];
    private array $singletons = [];
    private array $deferred = [];
    private Cache $cache;
    private EventDispatcher $events;

    private function __construct()
    {
        $this->cache = new Cache();
        $this->events = new EventDispatcher();
        $this->registerCoreServices();
    }

    public static function getInstance(): Container
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Register a service binding
     */
    public function bind(string $abstract, $concrete = null, bool $shared = false): void
    {
        if ($concrete === null) {
            $concrete = $abstract;
        }

        $this->bindings[$abstract] = [
            'concrete' => $concrete,
            'shared' => $shared
        ];

        if ($shared) {
            $this->singletons[$abstract] = true;
        }
    }

    /**
     * Register a singleton
     */
    public function singleton(string $abstract, $concrete = null): void
    {
        $this->bind($abstract, $concrete, true);
    }

    /**
     * Register a deferred service provider
     */
    public function defer(string $abstract, string $provider): void
    {
        $this->deferred[$abstract] = $provider;
    }

    /**
     * Resolve a service from the container
     */
    public function make(string $abstract)
    {
        // Return cached instance if singleton
        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        // Load deferred provider if needed
        if (isset($this->deferred[$abstract])) {
            $this->loadDeferredProvider($abstract);
        }

        // Get concrete implementation
        $concrete = $this->getConcrete($abstract);

        // Build the instance
        $instance = $this->build($concrete);

        // Cache if singleton
        if (isset($this->singletons[$abstract])) {
            $this->instances[$abstract] = $instance;
        }

        return $instance;
    }

    /**
     * Build an instance of the given concrete
     */
    private function build($concrete)
    {
        if ($concrete instanceof \Closure) {
            return $concrete($this);
        }

        if (is_string($concrete)) {
            if (class_exists($concrete)) {
                return $this->buildClass($concrete);
            }
            throw new \Exception("Class {$concrete} not found");
        }

        return $concrete;
    }

    /**
     * Build a class instance with dependency injection
     */
    private function buildClass(string $className)
    {
        $reflector = new \ReflectionClass($className);

        if (!$reflector->isInstantiable()) {
            throw new \Exception("Class {$className} is not instantiable");
        }

        $constructor = $reflector->getConstructor();

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

        $dependencies = $this->resolveDependencies($constructor->getParameters());

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

    /**
     * Resolve constructor dependencies
     */
    private function resolveDependencies(array $parameters): array
    {
        $dependencies = [];

        foreach ($parameters as $parameter) {
            $type = $parameter->getType();

            if ($type && !$type->isBuiltin()) {
                $dependencies[] = $this->make($type->getName());
            } elseif ($parameter->isDefaultValueAvailable()) {
                $dependencies[] = $parameter->getDefaultValue();
            } else {
                throw new \Exception("Unable to resolve dependency: {$parameter->getName()}");
            }
        }

        return $dependencies;
    }

    private function getConcrete(string $abstract)
    {
        if (isset($this->bindings[$abstract])) {
            return $this->bindings[$abstract]['concrete'];
        }

        return $abstract;
    }

    private function loadDeferredProvider(string $abstract): void
    {
        $provider = $this->deferred[$abstract];
        $providerInstance = new $provider($this);
        $providerInstance->register();
        unset($this->deferred[$abstract]);
    }

    private function registerCoreServices(): void
    {
        // Register core services with closures to avoid circular dependencies
        $this->singleton(Cache::class, function () {
            return $this->cache;
        });

        $this->singleton(EventDispatcher::class, function () {
            return $this->events;
        });

        $this->singleton(DiscoveryEngine::class, function () {
            return new DiscoveryEngine($this, $this->cache, $this->events);
        });

        $this->singleton(AssetManager::class, function () {
            return new AssetManager();
        });

        $this->singleton(WidgetManager::class, function () {
            return new WidgetManager($this, $this->cache);
        });

        $this->singleton(SettingsManager::class, function () {
            return new SettingsManager($this, $this->cache);
        });

        $this->singleton(WebhookManager::class, function () {
            return new WebhookManager($this, $this->cache, $this->events);
        });
    }

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

    public function getEvents(): EventDispatcher
    {
        return $this->events;
    }
}
