<?php

namespace WordpressModels\DependencyInjection\Metabox;

use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Reference;
use WordpressModels\DependencyInjection\Metabox\Attributes\AsMetabox;
use WordpressModels\PostMetaBox;

class MetaboxExtension extends Extension
{

    private ContainerInterface $container;

    public function load(array $configs, ContainerBuilder $container)
    {
        $this->container = $container;
        $container->registerAttributeForAutoconfiguration(AsMetabox::class, self::tagMetabox(...));

        // add the registry
        $container->register(MetaboxRegistry::class)
            ->addArgument(new Reference('service_container'))
            ->setPublic(true)
            ->setAutowired(true)
            ->setAutoconfigured(true);
    }

    /**
     * Add the 'metabox' tag to the definition.
     *
     * Sets the definition as public and lazy, and adds the 'metabox' tag to the definition.
     *
     * Also validates that the attributed class is not a subclass of {@see PostMetaBox}, which is intended for use without the Service Container.
     *
     * Auto-injects the metabox ID and screen from the attributed values.
     *
     * @param ChildDefinition $definition
     * @param AsMetabox $metabox
     * @param \ReflectionClass $reflection
     * @return void
     */
    public static function tagMetabox(ChildDefinition $definition, AsMetabox $metabox, \ReflectionClass $reflection): void
    {
        if ($reflection->isSubclassOf(PostMetaBox::class)) {
            throw new \InvalidArgumentException(sprintf('"%s" is used as a base class. This class is intended for use without the Service Container and should not be tagged with #[%s], use "%s" instead',
                $reflection->getName(), AsMetabox::class, AbstractMetabox::class));
        }

        if (!$reflection->isSubclassOf(AbstractMetabox::class)) {
            throw new \InvalidArgumentException(sprintf('The class "%s" must be a subclass of "%s"',
                $reflection->getName(), AbstractMetabox::class));
        }

        // set the definition as public and lazy
        $definition->setPublic(true);
        $definition->setLazy(true);

        // inject id and screen with the attributed values via
        $autoInjectProps = [
            'id' => 'withId',
            'screen' => 'withScreen'
        ];
        foreach ($autoInjectProps as $param => $method) {
            $values = [$metabox->{$param}];
            // use immutable method calls (true) to avoid overwriting previous method calls
            $definition->addMethodCall($method, $values, true);
        }

        // add the tag
        $definition->addTag('metabox', [
            'id' => $metabox->id,
            'title' => $metabox->title,
            'screen' => $metabox->screen,
            'context' => $metabox->context,
            'priority' => $metabox->priority,
        ]);
    }
}