<?php

namespace Velis\Db\EntityHook;

use Phalcon\Di\DiInterface;
use ReflectionClass;
use RuntimeException;
use Throwable;
use Velis\Model\DataObject;

class EntityHookExecutor
{
    public function __construct(private readonly DiInterface $di)
    {
    }

    public function executeHooks(EventType $event, DataObject $object): void
    {
        $hooks = $this->getHooks($event, $object);

        foreach ($hooks as $hook) {
            if (!is_callable($hook, false, $callableName)) {
                throw new RuntimeException(sprintf('Hook "%s" does not exist', $callableName));
            }

            try {
                // phpcs:ignore PHPCS_SecurityAudit.BadFunctions.FunctionHandlingFunctions.WarnFunctionHandling
                call_user_func($hook, $object);
            } catch (Throwable $e) {
                $message = sprintf('Error executing hook "%s": %s', $callableName, $e->getMessage());
                throw new RuntimeException($message, 0, $e);
            }
        }
    }

    /**
     * @return array<array{0: object, 1: string}>
     */
    private function getHooks(EventType $event, DataObject $object): array
    {
        $attributes = [];
        $reflection = new ReflectionClass($object);

        while ($reflection && $reflection->name !== DataObject::class) {
            $attributes = array_merge($attributes, $reflection->getAttributes(HasHook::class));
            $reflection = $reflection->getParentClass();
        }

        $hooks = [];
        foreach ($attributes as $attribute) {
            /** @var HasHook $hook */
            $hook = $attribute->newInstance();
            if ($hook->getEvent() === $event) {
                $hooks[] = [
                    $this->di->get($hook->getClass()),
                    $hook->getMethod(),
                ];
            }
        }

        return $hooks;
    }
}
