<?php

/**
 * League.Csv (https://csv.thephpleague.com)
 *
 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace GeminiLabs\League\Csv\Serializer;

use Attribute;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use ValueError;

#[Attribute(Attribute::TARGET_CLASS)]
final class MapRecord
{
    public function __construct(
        /** @var array<string> $afterMapping */
        public readonly array $afterMapping = [],
        public readonly ?bool $convertEmptyStringToNull = null,
        public readonly bool $trimFieldValueBeforeCasting = false,
    ) {
        foreach ($this->afterMapping as $method) {
            is_string($method) || throw new ValueError('The method names must be string.');
        }
    }

    /**
     * @return array<ReflectionMethod>
     */
    public function afterMappingMethods(ReflectionClass $class): array
    {
        $methods = [];
        foreach ($this->afterMapping as $method) {
            try {
                $accessor = $class->getMethod($method);
            } catch (ReflectionException $exception) {
                throw new MappingFailed('The method `'.$method.'` is not defined on the `'.$class->getName().'` class.', 0, $exception);
            }

            0 === $accessor->getNumberOfRequiredParameters() || throw new MappingFailed('The method `'.$class->getName().'::'.$accessor->getName().'` has too many required parameters.');
            $methods[] = $accessor;
        }

        return $methods;
    }

    /**
     * @throws MappingFailed
     */
    public static function tryFrom(ReflectionClass $class): ?self
    {
        $attributes = $class->getAttributes(self::class, ReflectionAttribute::IS_INSTANCEOF);
        $nbAttributes = count($attributes);

        return match ($nbAttributes) {
            0 => null,
            1 => $attributes[0]->newInstance(),
            default => throw new MappingFailed('Using more than one `'.self::class.'` attribute on a class property or method is not supported.'),
        };
    }
}
