<?php
/**
 * Copyright since 2025 InPost S.A.
 *
 * For the full license information, please view the LICENSE file bundled with the module.
 *
 * @author InPost S.A.
 * @copyright since 2025 InPost S.A.
 * @license MIT
 */

declare(strict_types=1);

namespace InPost\International\Hook;

use InPost\International\Asset\Event\AdminControllerAssetsRegisteredEvent;
use InPost\International\Asset\Event\FrontControllerAssetsRegisteredEvent;
use InPost\International\Checkout\Event\CarrierProcessEvent;
use InPost\International\Checkout\Event\DisplayCarrierContentEvent;
use InPost\International\Checkout\Event\StepCompleteValidationEvent;
use InPost\International\Hook\Event\Admin\DisplayGridTableAfterEvent;
use InPost\International\Hook\Event\DisplayHook;
use InPost\International\Hook\Event\Front\ControllerInitializedEvent;
use InPost\International\Hook\Event\Hook;
use InPost\International\Hook\Event\HookInterface;
use InPost\International\Hook\Event\Mail\GetExtraTemplateVariablesEvent;
use InPost\International\Installer\AvailableHooksProviderInterface;
use InPost\International\Order\Event\Admin\BuildGridDefinitionEvent;
use InPost\International\Order\Event\Admin\BuildOrdersQueriesEvent;
use InPost\International\Order\Event\Admin\DisplayOrderTabContentEvent;
use InPost\International\Order\Event\Admin\DisplayOrderTabLinkEvent;
use InPost\International\Order\Event\DisplayOrderConfirmationEvent;
use InPost\International\Order\Event\DisplayOrderDetailsEvent;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

if (!defined('_PS_VERSION_')) {
    exit;
}

final class HookEventFactory implements HookEventFactoryInterface, AvailableHooksProviderInterface
{
    private const CLASS_MAP = [
        /* admin */
        AdminControllerAssetsRegisteredEvent::NAME => AdminControllerAssetsRegisteredEvent::class,
        DisplayGridTableAfterEvent::NAME => DisplayGridTableAfterEvent::class,
        /* front */
        FrontControllerAssetsRegisteredEvent::NAME => FrontControllerAssetsRegisteredEvent::class,
        ControllerInitializedEvent::NAME => ControllerInitializedEvent::class,
        /* checkout */
        CarrierProcessEvent::NAME => CarrierProcessEvent::class,
        StepCompleteValidationEvent::NAME => StepCompleteValidationEvent::class,
        DisplayCarrierContentEvent::NAME => DisplayCarrierContentEvent::class,
        /* mails */
        GetExtraTemplateVariablesEvent::NAME => GetExtraTemplateVariablesEvent::class,
        /* orders */
        DisplayOrderConfirmationEvent::NAME => DisplayOrderConfirmationEvent::class,
        DisplayOrderDetailsEvent::NAME => DisplayOrderDetailsEvent::class,
        /* admin orders */
        DisplayOrderTabContentEvent::NAME => DisplayOrderTabContentEvent::class,
        DisplayOrderTabLinkEvent::NAME => DisplayOrderTabLinkEvent::class,
        BuildGridDefinitionEvent::NAME => BuildGridDefinitionEvent::class,
        BuildOrdersQueriesEvent::NAME => BuildOrdersQueriesEvent::class,
    ];

    /**
     * @var string[]
     */
    private $hookNames;

    /**
     * @var array<string, class-string<HookInterface>>
     */
    private $classMap;

    /**
     * @var DenormalizerInterface
     */
    private $denormalizer;

    /**
     * @param array<string, class-string<HookInterface>> $classMap
     */
    public function __construct(DenormalizerInterface $denormalizer, array $classMap = [])
    {
        $this->setClassMap(array_merge(self::CLASS_MAP, $classMap));
        $this->denormalizer = $denormalizer;
    }

    /**
     * {@inheritDoc}
     */
    public function create(string $name, array $parameters): HookInterface
    {
        $normalizedName = self::normalizeHookName($name);

        if (isset($this->classMap[$normalizedName])) {
            $class = $this->classMap[$normalizedName];

            return $this->denormalize($parameters, $class);
        }

        if (\Hook::isDisplayHookName($name)) {
            return new DisplayHook($name, $parameters);
        }

        return new Hook($name, $parameters);
    }

    public function getAvailableHookNames(): array
    {
        return $this->hookNames;
    }

    /**
     * @template T
     *
     * @param array<string, mixed> $data
     * @param class-string<T> $class
     *
     * @return T
     */
    private function denormalize(array $data, string $class)
    {
        return $this->denormalizer->denormalize($data, $class);
    }

    private function setClassMap(array $classMap): void
    {
        $this->hookNames = array_keys($classMap);
        $this->classMap = [];

        foreach ($classMap as $name => $class) {
            $name = self::normalizeHookName($name);
            $this->classMap[$name] = $class;
        }
    }

    private static function normalizeHookName(string $name): string
    {
        return strtolower($name);
    }
}
