<?php

namespace WordpressModels\DependencyInjection\HookAttributes;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

/**
 * Container compiler pass to register hooks from attributes to the {@see ContainerHookRegistry}.
 *
 * This pass is run before the container is dumped to register all hooks with the {@see ContainerHookRegistry}.
 *
 * This compiler pass registers all services with the 'action', 'filter' and 'ajax_action' tags to the
 * {@see ContainerHookRegistry} service. This is done by adding a method call to the {@see ContainerHookRegistry}
 * service for each tagged service.
 */
class HookAttributesCompilerPass implements CompilerPassInterface
{

    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition(ContainerHookRegistry::class)) {
            return;
        }

        $service = $container->getDefinition(ContainerHookRegistry::class);

        // find all services with the action and filter tags
        $this->registerMethodCalls($container, 'action', $service);
        $this->registerMethodCalls($container, 'filter', $service);

        // ajax actions
        foreach ($container->findTaggedServiceIds('ajax_action') as $id => $tags) {
            foreach ($tags as $tag) {
                $method = $tag['method'];
                unset($tag['method']);
                $handler = [new Reference($id), $method];

                // prepare for NamedArgumentResolver
                $args = ['$handler' => $handler];
                foreach ($tag as $key => $value) {
                    $args['$' . $key] = $value;
                }

                // add the method call
                $service->addMethodCall('addAjaxHook', $args);
            }
        }
    }

    /**
     * @param ContainerBuilder $container
     * @param string $hookType
     * @param Definition $hookService
     * @return array
     */
    public function registerMethodCalls(ContainerBuilder $container, string $hookType, Definition $hookService): void
    {
        foreach ($container->findTaggedServiceIds($hookType) as $id => $tags) {
            foreach ($tags as $tag) {
                $isAdminHook = $tag['admin'] ?: in_array($tags['hook'], AdminHooks::HOOKS);
                if ($isAdminHook && !is_admin()) {
                    continue;
                }
                // create the callable reference
                $callable = [new Reference($id), $tag['method']];

                // todo: support for Autowire, Target
                $injectParameters = array_map(fn($service) => new Reference($service), $tag['injectServices'] ?? []);

                // add the method call
                $hookService->addMethodCall('addHook', [
                    $id,
                    $hookType,
                    $callable,
                    $tag['hook'],
                    $tag['priority'],
                    $tag['accepted_args'],
                    $tag['when'],
                    $tag['admin'],
                    $injectParameters
                ]);
            }
        }
    }

}