<?php

/**
 * Базовий клас для фасадів
 *
 * Фасади надають статичний доступ до сервісів, зареєстрованих в контейнері
 *
 * @package Flowaxy\Support\Facades
 */

declare(strict_types=1);

namespace Flowaxy\Support\Facades;

use Flowaxy\Core\Contracts\ContainerInterface;
use Flowaxy\Core\System\Container\Container;

abstract class Facade
{
    /**
     * Отримання імені сервісу в контейнері
     *
     * @return string
     */
    abstract protected static function getFacadeAccessor(): string;

    /**
     * Отримання контейнера залежностей
     *
     * @return ContainerInterface
     * @throws \RuntimeException
     */
    protected static function getContainer(): ContainerInterface
    {
        if (! isset($GLOBALS['engineContainer']) || ! $GLOBALS['engineContainer'] instanceof ContainerInterface) {
            // Спробуємо ініціалізувати Container, якщо він ще не ініціалізований
            try {
                $container = new Container();
                $GLOBALS['engineContainer'] = $container;
                return $container;
            } catch (\Throwable $e) {
                // Якщо не вдалося, викидаємо помилку
                throw new \RuntimeException('Container is not initialized. Make sure Kernel is booted.', 0, $e);
            }
        }

        return $GLOBALS['engineContainer'];
    }

    /**
     * Отримання екземпляра сервісу
     *
     * @return object
     */
    protected static function getFacadeRoot(): object
    {
        $container = static::getContainer();
        $accessor = static::getFacadeAccessor();

        if ($container->has($accessor)) {
            $result = $container->make($accessor);
            // Якщо контейнер повернув Closure, викликаємо його
            if ($result instanceof \Closure) {
                $result = $result();
            }
            // Якщо це lazy proxy (анонімний клас), отримуємо реальний інстанс
            // Lazy proxy має метод __call, тому перевіряємо це
            if (is_object($result) && get_class($result) !== $accessor && method_exists($result, '__call')) {
                // Це lazy proxy, отримуємо реальний інстанс через виклик будь-якого методу
                // Але для типізованих методів (як Router::manager()) це не спрацює
                // Тому використовуємо fallback через getInstance() якщо доступний
                if (class_exists($accessor) && method_exists($accessor, 'getInstance')) {
                    return $accessor::getInstance();
                }
            }
            // Перевіряємо, що отримали об'єкт
            if (is_object($result)) {
                return $result;
            }
        }

        // Fallback: якщо сервіс не в контейнері, намагаємося отримати через getInstance()
        if (class_exists($accessor) && method_exists($accessor, 'getInstance')) {
            return $accessor::getInstance();
        }

        // Дополнительная диагностика для Hash facade
        $containerClass = get_class($container);
        $hasMethod = method_exists($container, 'has') ? 'yes' : 'no';
        $containerHas = $container->has($accessor) ? 'yes' : 'no';
        $registeredServices = method_exists($container, 'getBindings') ? 'available' : 'not available';

        throw new \RuntimeException(
            "Facade accessor '{$accessor}' not found in container and no getInstance() method available. " .
            "Container class: {$containerClass}, has() method: {$hasMethod}, container->has('{$accessor}'): {$containerHas}, registered services: {$registeredServices}"
        );
    }

    /**
     * Статичний виклик методів через фасад
     *
     * @param string $method
     * @param array<int, mixed> $args
     * @return mixed
     */
    public static function __callStatic(string $method, array $args): mixed
    {
        $instance = static::getFacadeRoot();

        try {
            $result = $instance->$method(...$args);
        } finally {
            // Очистка контекста после фазы работы фасада
            static::clearContext();
        }

        return $result;
    }

    /**
     * Очистка контекста после фазы работы фасада
     * Предотвращает утечки памяти и выход за пределы квоты
     *
     * @return void
     */
    protected static function clearContext(): void
    {
        // Очищаем глобальные переменные, связанные с фасадом
        if (isset($GLOBALS['_FLOWAXY_FACADE_CONTEXT'])) {
            unset($GLOBALS['_FLOWAXY_FACADE_CONTEXT']);
        }

        // Очищаем статические переменные, если они есть
        // Каждый фасад может переопределить этот метод для своей очистки
    }
}
