<?php
declare(strict_types=1);

namespace App\Framework\Core;

use ReflectionClass;
use ReflectionMethod;

final readonly class AttributeProcessor
{
    /** @var array<string, AttributeMapper> */
    private array $mapperMap;

    /**
     * @param AttributeMapper[] $mappers
     */
    public function __construct(array $mappers)
    {
        $mapperMap = [];
        foreach ($mappers as $mapper) {
            $mapperMap[$mapper->getAttributeClass()] = $mapper;
        }
        $this->mapperMap = $mapperMap;
    }

    /**
     * Verarbeitet alle Attribute einer Klasse
     */
    public function processClass(ReflectionClass $refClass, array &$results): void
    {
        $this->processAttributes($refClass, $results);

        foreach ($refClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
            $this->processAttributes($method, $results);
        }
    }

    /**
     * Verarbeitet die Attribute eines Elements (Klasse oder Methode)
     */
    private function processAttributes(ReflectionClass|ReflectionMethod $ref, array &$results): void
    {
        foreach ($ref->getAttributes() as $attribute) {
            $attrName = $attribute->getName();
            if (!isset($this->mapperMap[$attrName])) {
                continue;
            }

            $mapped = $this->mapperMap[$attrName]->map($ref, $attribute->newInstance());

            if ($mapped !== null && $ref instanceof ReflectionMethod) {
                $mapped['parameters'] = $this->extractMethodParameters($ref);
            }

            if ($mapped !== null) {
                $results[$attrName][] = $mapped;
            }
        }
    }

    /**
     * Extrahiert Parameter-Informationen aus einer Methode
     */
    private function extractMethodParameters(ReflectionMethod $method): array
    {
        $params = [];
        foreach ($method->getParameters() as $param) {
            $paramData = [
                'name' => $param->getName(),
                'type' => $param->getType()?->getName(),
                'isBuiltin' => $param->getType()?->isBuiltin() ?? false,
                'hasDefault' => $param->isDefaultValueAvailable(),
                'default' => $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null,
                'attributes' => array_map(
                    fn ($a) => $a->getName(),
                    $param->getAttributes()
                ),
            ];
            $params[] = $paramData;
        }

        return $params;
    }

}
