<?php

/**
 * Реєстр хуків з метаданими
 *
 * Зберігає метадані про хуки: опис, версію, залежності
 *
 * @package Flowaxy\Core\Hooks
 * @version 1.0.0
 */

declare(strict_types=1);

namespace Flowaxy\Core\Hooks;

use Flowaxy\Core\Contracts\HookRegistryInterface;
use Flowaxy\Support\Facades\Log;
use Throwable;

final class HookRegistry implements HookRegistryInterface
{
    /**
     * @var array<string, array{
     *     description: string,
     *     version: string,
     *     dependencies: array<string>,
     *     type: HookType,
     *     registered_at: int
     * }>
     */
    private array $metadata = [];

    private static ?self $instance = null;

    /**
     * Получить экземпляр (singleton)
     *
     * @return self
     */
    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Очистить registry (для hot reload)
     *
     * @return void
     */
    public static function clear(): void
    {
        if (self::$instance !== null) {
            self::$instance->metadata = [];
        }
    }

    /**
     * Реєстрація хука з метаданими
     *
     * @param string $hookName Назва хука
     * @param HookType $type Тип хука
     * @param string $description Опис хука
     * @param string $version Версія хука
     * @param array<string> $dependencies Залежності від інших хуків
     * @return void
     */
    public function register(
        string $hookName,
        HookType $type,
        string $description = '',
        string $version = '1.0.0',
        array $dependencies = []
    ): void {
        try {
            Log::Debug('HookRegistry::register: Registering hook with metadata', [
                'hook' => $hookName,
                'type' => $type->value,
                'version' => $version,
                'dependencies' => $dependencies,
            ]);
        } catch (Throwable $e) {
            // Ignore logging errors
        }

        if (!isset($this->metadata[$hookName])) {
            $this->metadata[$hookName] = [
                'description' => $description,
                'version' => $version,
                'dependencies' => $dependencies,
                'type' => $type,
                'registered_at' => time(),
            ];

            try {
                Log::Info('HookRegistry::register: Hook registered with metadata', [
                    'hook' => $hookName,
                    'type' => $type->value,
                ]);
            } catch (Throwable $e) {
                // Ignore logging errors
            }
        } else {
            // Оновлюємо метадані, якщо хук вже зареєстровано
            try {
                Log::Debug('HookRegistry::register: Updating existing hook metadata', [
                    'hook' => $hookName,
                ]);
            } catch (Throwable $e) {
                // Ignore logging errors
            }
            $this->metadata[$hookName]['description'] = $description ?: $this->metadata[$hookName]['description'];
            $this->metadata[$hookName]['version'] = $version;
            $this->metadata[$hookName]['dependencies'] = array_unique(
                array_merge($this->metadata[$hookName]['dependencies'], $dependencies)
            );
        }
    }

    /**
     * Отримання метаданих хука
     *
     * @param string $hookName Назва хука
     * @return array<string, mixed>|null Метадані або null якщо хук не знайдено
     */
    public function getMetadata(string $hookName): ?array
    {
        return $this->metadata[$hookName] ?? null;
    }

    /**
     * Отримання залежностей хука
     *
     * @param string $hookName Назва хука
     * @return array<string> Масив назв залежних хуків
     */
    public function getDependencies(string $hookName): array
    {
        return $this->metadata[$hookName]['dependencies'] ?? [];
    }

    /**
     * Перевірка наявності хука в реєстрі
     *
     * @param string $hookName Назва хука
     * @return bool
     */
    public function has(string $hookName): bool
    {
        return isset($this->metadata[$hookName]);
    }

    /**
     * Отримання всіх зареєстрованих хуків
     *
     * @return array<string, array<string, mixed>>
     */
    public function getAll(): array
    {
        return $this->metadata;
    }

    /**
     * Отримання хуків за типом
     *
     * @param HookType $type Тип хука
     * @return array<string, array<string, mixed>>
     */
    public function getByType(HookType $type): array
    {
        return array_filter(
            $this->metadata,
            fn(array $meta) => $meta['type'] === $type
        );
    }

    /**
     * Очищення реєстру
     *
     * @return void
     */
    public function flush(): void
    {
        $this->metadata = [];
    }

    /**
     * Видалення хука з реєстру
     *
     * @param string $hookName Назва хука
     * @param callable|null $listener Callback для видалення (не використовується в HookRegistry, тільки для сумісності з інтерфейсом)
     * @return void
     */
    public function remove(string $hookName, ?callable $listener = null): void
    {
        // HookRegistry зберігає тільки метадані, тому ігноруємо $listener
        unset($this->metadata[$hookName]);
    }

    /**
     * Реєстрація action (для сумісності з інтерфейсом)
     * HookRegistry зберігає тільки метадані, реальна реєстрація відбувається в HookManager
     *
     * @param string $hookName Назва хука
     * @param callable $listener Callback функція
     * @param int $priority Пріоритет
     * @param bool $once Виконати тільки один раз
     * @return void
     */
    public function registerAction(string $hookName, callable $listener, int $priority = 10, bool $once = false): void
    {
        // Реєструємо метадані, якщо хук ще не зареєстровано
        if (!isset($this->metadata[$hookName])) {
            $this->register($hookName, HookType::Action);
        }
        // Реальна реєстрація слухача відбувається в HookManager
    }

    /**
     * Реєстрація filter (для сумісності з інтерфейсом)
     * HookRegistry зберігає тільки метадані, реальна реєстрація відбувається в HookManager
     *
     * @param string $hookName Назва хука
     * @param callable $listener Callback функція
     * @param int $priority Пріоритет
     * @return void
     */
    public function registerFilter(string $hookName, callable $listener, int $priority = 10): void
    {
        // Реєструємо метадані, якщо хук ще не зареєстровано
        if (!isset($this->metadata[$hookName])) {
            $this->register($hookName, HookType::Filter);
        }
        // Реальна реєстрація слухача відбувається в HookManager
    }

    /**
     * Отримання слухачів хука
     * HookRegistry не зберігає слухачі, вони зберігаються в HookManager
     *
     * @param string $hookName Назва хука
     * @return array<int, HookListener> Порожній масив, оскільки слухачі зберігаються в HookManager
     */
    public function getListeners(string $hookName): array
    {
        // HookRegistry не зберігає слухачі, вони зберігаються в HookManager
        // Повертаємо порожній масив для сумісності з інтерфейсом
        return [];
    }
}
