<?php

declare(strict_types=1);

namespace App\Service\Utils;

use App\Attribute\BillingSettingDefaultValue;
use App\Attribute\OrganizationDefaultValue;
use App\Entity\Access\Access;
use Doctrine\ORM\EntityManagerInterface;

/**
 * Class EntityUtils : Gestion des valeurs par défauts devant être présentes dans les entités.
 */
class EntityUtils
{
    /**
     * Cache of the mapping between entity names and fully qualified names.
     *
     * @var array<string, string>
     */
    protected array $entityNamesMappingCache = [];

    public function __construct(
        private readonly EntityManagerInterface $entityManager,
    ) {
    }

    /**
     * @throws \ReflectionException
     */
    public function defaultValueSettersByAccess(mixed $entity, ?Access $access): void
    {
        if ($access === null) {
            // Cas des internal requests (ex: création d'organisation)
            return;
        }
        $this->organizationDefaultValue($entity, $access);
        $this->billingSettingDefaultValueDefaultValue($entity, $access);
    }

    /**
     * @throws \ReflectionException
     */
    protected function organizationDefaultValue(mixed $entity, Access $access): void
    {
        $reflection = new \ReflectionClass($entity::class);
        $organizationDefaultValue = $reflection->getAttributes(OrganizationDefaultValue::class)[0] ?? null;
        $fieldName = $organizationDefaultValue?->getArguments()['fieldName'] ?? null;
        if ($fieldName) {
            $entity->{sprintf('set%s', ucfirst($fieldName))}(...[$access->getOrganization()]);
        }
    }

    /**
     * @throws \ReflectionException
     */
    protected function billingSettingDefaultValueDefaultValue(mixed $entity, Access $access): void
    {
        $reflection = new \ReflectionClass($entity::class);
        $billingSettingDefaultValueDefault = $reflection->getAttributes(BillingSettingDefaultValue::class)[0] ?? null;
        $fieldName = $billingSettingDefaultValueDefault?->getArguments()['fieldName'] ?? null;
        if ($fieldName) {
            $entity->{sprintf('set%s', ucfirst($fieldName))}(...[$access->getOrganization()?->getBillingSetting()]);
        }
    }

    /**
     * Extract an entity base name from a fully qualified name
     * Ex: '\App\Entity\Core\File' => 'File'.
     */
    public function getEntityNameFromFullName(string $fullName): string
    {
        $parts = explode('\\', $fullName);

        return array_pop($parts);
    }

    /**
     * Find the fully qualified name matching the given entity base name.
     *
     * Ex: 'File' => '\App\Entity\Core\File'
     */
    public function getFullNameFromEntityName(string $entityName): string
    {
        if (empty($this->entityNamesMappingCache)) {
            $this->populateEntityNamesMappingCache();
        }
        if (!isset($this->entityNamesMappingCache[$entityName])) {
            throw new \LogicException('No entity found for name `'.$entityName.'`');
        }

        return $this->entityNamesMappingCache[$entityName];
    }

    /**
     * Populates a cache mapping entity base names to their fully qualified class names.
     */
    protected function populateEntityNamesMappingCache(): void
    {
        $this->entityNamesMappingCache = [];
        $metadata = $this->entityManager->getMetadataFactory()->getAllMetadata();
        foreach ($metadata as $entityMetadata) {
            $baseName = $this->getEntityNameFromFullName($entityMetadata->getName());
            $this->entityNamesMappingCache[$baseName] = $entityMetadata->getName();
        }
    }

    /**
     * Get the namespace from a fully qualified name
     * Ex: '\App\Entity\Core\File' => 'App\Entity\Core'.
     */
    public function getNamespaceFromName(string $entityName): ?string
    {
        $parts = explode('\\', $entityName);
        array_pop($parts);

        return ltrim(implode('\\', $parts), '\\');
    }
}
