<?php

declare(strict_types=1);

namespace MauticPlugin\DealFlowBundle;

use Mautic\IntegrationsBundle\Bundle\AbstractPluginBundle;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

final class DealFlowBundle extends AbstractPluginBundle
{
    public function build(ContainerBuilder $container): void
    {
        parent::build($container);

        $container->addCompilerPass(new class implements CompilerPassInterface {
            public function process(ContainerBuilder $container): void
            {
                // --- Controller fixups ---
                // When Mautic auto-discovers controllers AND they're also in
                // config.php's controllers section, ServicePass may skip adding
                // the controller.service_arguments tag (which enables action
                // method parameter injection for forward() sub-requests).
                // Also inject constructor args for CommonController on M7+.
                // On Symfony 5.4 (Mautic 5/6), AbstractController uses
                // ContainerAwareTrait::setContainer() which is NOT marked
                // #[Required], so setAutowired(true) + container.service_subscriber
                // does NOT trigger container injection. We must add an explicit
                // method call. The Autowire attribute class exists only in Symfony 6.1+.
                $needsExplicitSetContainer = !class_exists('Symfony\Component\DependencyInjection\Attribute\Autowire');

                $args = self::resolveConstructorArgs();
                foreach ($container->getDefinitions() as $id => $definition) {
                    $class = ltrim((string) $definition->getClass(), '\\');
                    $isDealFlowControllerClass = str_starts_with($class, __NAMESPACE__.'\\Controller\\') && str_ends_with($class, 'Controller');
                    if (!str_starts_with($id, 'dealflow.controller.') && !$isDealFlowControllerClass) {
                        continue;
                    }
                    // Ensure the tag exists — required for action method injection
                    if (!$definition->hasTag('controller.service_arguments')) {
                        $definition->addTag('controller.service_arguments');
                    }
                    // container.service_subscriber builds the locator; setAutowired(true)
                    // causes AutowireRequiredMethodsPass to register the #[Required]
                    // setContainer() call that AbstractController needs in M7.
                    if (!$definition->hasTag('container.service_subscriber')) {
                        $definition->addTag('container.service_subscriber');
                    }
                    $definition->setAutowired(true);
                    // Inject constructor args if needed (M7 has constructor)
                    if ([] !== $args && [] === $definition->getArguments()) {
                        $definition->setArguments($args);
                    }
                    // M5/M6: explicit setContainer() since ContainerAwareTrait
                    // doesn't have #[Required] on the method
                    if ($needsExplicitSetContainer) {
                        $definition->addMethodCall('setContainer', [new Reference('service_container')]);
                    }
                }

                // Also ensure FQCN-keyed auto-discovered definitions have the tag
                // (forward() resolves by FQCN, not by service ID)
                $controllerFqcns = [
                    Controller\Api\DealApiController::class,
                    Controller\ApiDocsController::class,
                    Controller\DashboardAjaxController::class,
                    Controller\DealAttachmentController::class,
                    Controller\DealBatchController::class,
                    Controller\DealListController::class,
                    Controller\DealFormController::class,
                    Controller\DealViewController::class,
                    Controller\DealViewAjaxController::class,
                    Controller\DealController::class,
                    Controller\DealFieldController::class,
                    Controller\InboundWebhookSourceController::class,
                    Controller\PipelineController::class,
                    Controller\ReportController::class,
                    Controller\StageController::class,
                    Controller\TaskController::class,
                    Controller\UpdateController::class,
                    Controller\WebhookController::class,
                ];
                foreach ($controllerFqcns as $fqcn) {
                    if (!$container->hasDefinition($fqcn)) {
                        $container->setDefinition($fqcn, new Definition($fqcn));
                    }

                    $def = $container->getDefinition($fqcn);
                    if (!$def->hasTag('controller.service_arguments')) {
                        $def->addTag('controller.service_arguments');
                    }
                    if (!$def->hasTag('container.service_subscriber')) {
                        $def->addTag('container.service_subscriber');
                    }
                    $def->setAutowired(true);
                    if ([] !== $args && [] === $def->getArguments()) {
                        $def->setArguments($args);
                    }
                    $def->setPublic(true);
                    if ($needsExplicitSetContainer) {
                        $def->addMethodCall('setContainer', [new Reference('service_container')]);
                    }
                }

                // --- GlobalSearch compat ---
                if ($container->has(\Mautic\CoreBundle\Service\GlobalSearch::class)) {
                    $container->setAlias('dealflow.service.global_search', \Mautic\CoreBundle\Service\GlobalSearch::class);

                    if ($container->hasDefinition('dealflow.global_search_subscriber')) {
                        $def = $container->getDefinition('dealflow.global_search_subscriber');
                        $methodCalls = array_values(array_filter(
                            $def->getMethodCalls(),
                            static fn (array $call): bool => $call[0] !== 'setGlobalSearch'
                        ));
                        $methodCalls[] = ['setGlobalSearch', [new Reference('dealflow.service.global_search')]];
                        $def->setMethodCalls($methodCalls);
                    }
                } else {
                    if ($container->hasDefinition('dealflow.global_search_subscriber')) {
                        $def = $container->getDefinition('dealflow.global_search_subscriber');
                        $def->setMethodCalls(
                            array_filter(
                                $def->getMethodCalls(),
                                static fn (array $call): bool => $call[0] !== 'setGlobalSearch'
                            )
                        );
                    }
                }

                // --- Remove value objects from container ---
                foreach ([
                    Service\LicenseResult::class,
                    Service\UpdateCheckResult::class,
                    Service\UpdateResult::class,
                    ViewModel\DealViewViewModel::class,
                    ViewModel\PipelineViewModel::class,
                ] as $valueObject) {
                    if ($container->hasDefinition($valueObject)) {
                        $container->removeDefinition($valueObject);
                    }
                }
            }

            /**
             * @return Reference[]
             */
            private static function resolveConstructorArgs(): array
            {
                $class = \Mautic\CoreBundle\Controller\CommonController::class;
                if (!class_exists($class)) {
                    return [];
                }

                $ref = new \ReflectionClass($class);
                $constructor = $ref->getConstructor();
                if (null === $constructor) {
                    return []; // M5/M6 ContainerAware — no constructor
                }

                $args = [];
                foreach ($constructor->getParameters() as $param) {
                    $type = $param->getType();
                    if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) {
                        $args[] = new Reference($type->getName());
                    } else {
                        return []; // Can't resolve — bail
                    }
                }

                return $args;
            }
        });
    }
}
