<?php

/*
 * This file is part of the Serenity package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Serenity\CoreBundle\Shared\ObjectMapper\Metadata;

use Symfony\Component\ObjectMapper\Attribute\Map;
use Symfony\Component\ObjectMapper\Metadata\Mapping;
use Symfony\Component\ObjectMapper\Metadata\ObjectMapperMetadataFactoryInterface;
use Symfony\Component\ObjectMapper\ObjectMapperInterface;

final readonly class MapStructMapperMetadataFactory implements ObjectMapperMetadataFactoryInterface
{
    /**
     * @param class-string<ObjectMapperInterface> $mapperClass The FQCN of the mapper service class
     */
    public function __construct(private string $mapperClass)
    {
        if (!is_a($this->mapperClass, ObjectMapperInterface::class, true)) {
            throw new \RuntimeException(\sprintf('Mapper class "%s" must implement "%s".', $this->mapperClass, ObjectMapperInterface::class));
        }
    }

    public function create(object $object, ?string $property = null, array $context = []): array
    {
        $refl = new \ReflectionClass($this->mapperClass);

        $mapConfigs = [];
        $sourceIdentifier = $property ?? $object::class;

        // read attributes from the map method (for property mapping) or the class (for class mapping)
        if ($property) {
            if (!$refl->hasMethod('map')) {
                throw new \RuntimeException(\sprintf('Mapper class "%s" must define a "map" method to support property mapping.', $this->mapperClass));
            }

            $attributesSource = $refl->getMethod('map');
        } else {
            $attributesSource = $refl;
        }

        foreach ($attributesSource->getAttributes(Map::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
            $map = $attribute->newInstance();

            if ($map->source === $sourceIdentifier) {
                $mapConfigs[] = new Mapping($map->target, $map->source, $map->if, $map->transform);
            }
        }

        if ($property && empty($mapConfigs)) {
            $mapConfigs[] = new Mapping(target: $property, source: $property);
        }

        return $mapConfigs;
    }
}
