<?php

declare (strict_types=1);
namespace OCA\Talk\Vendor\CuyZ\Valinor\Normalizer\Transformer\Compiler\TypeFormatter;

use OCA\Talk\Vendor\CuyZ\Valinor\Compiler\Library\NewAttributeNode;
use OCA\Talk\Vendor\CuyZ\Valinor\Compiler\Library\TypeAcceptNode;
use OCA\Talk\Vendor\CuyZ\Valinor\Compiler\Native\AnonymousClassNode;
use OCA\Talk\Vendor\CuyZ\Valinor\Compiler\Native\ComplianceNode;
use OCA\Talk\Vendor\CuyZ\Valinor\Compiler\Node;
use OCA\Talk\Vendor\CuyZ\Valinor\Definition\AttributeDefinition;
use OCA\Talk\Vendor\CuyZ\Valinor\Normalizer\Transformer\Compiler\TransformerDefinitionBuilder;
use OCA\Talk\Vendor\CuyZ\Valinor\Type\Type;
use WeakMap;
use function array_keys;
use function array_map;
use function preg_replace;
use function serialize;
use function sha1;
use function strtolower;
/** @internal */
final class RegisteredTransformersFormatter implements TypeFormatter
{
    public function __construct(
        private Type $type,
        private TypeFormatter $delegate,
        /** @var array<int, Type> */
        private array $transformerTypes,
        /** @var list<AttributeDefinition> */
        private array $transformerAttributes = []
    )
    {
    }
    public function formatValueNode(ComplianceNode $valueNode): Node
    {
        return Node::this()->callMethod(method: $this->methodName(), arguments: [$valueNode, Node::variable('references')]);
    }
    public function manipulateTransformerClass(AnonymousClassNode $class, TransformerDefinitionBuilder $definitionBuilder): AnonymousClassNode
    {
        $methodName = $this->methodName();
        if ($class->hasMethod($methodName)) {
            return $class;
        }
        $class = $this->delegate->manipulateTransformerClass($class, $definitionBuilder);
        $nodes = [Node::variable('next')->assign(Node::shortClosure($this->delegate->formatValueNode(Node::variable('value'))))->asExpression(), ...array_map(fn(int $key, Type $transformerType) => Node::if(condition: new TypeAcceptNode(Node::variable('value'), $transformerType), body: Node::variable('next')->assign(Node::shortClosure(return: Node::this()->access('transformers')->key(Node::value($key))->call(arguments: [Node::variable('value'), Node::variable('next')])))->asExpression()), array_keys($this->transformerTypes), $this->transformerTypes), ...array_map(function (AttributeDefinition $attribute) {
            $node = Node::variable('next')->assign(Node::shortClosure(return: (new NewAttributeNode($attribute))->wrap()->callMethod(method: 'normalize', arguments: [Node::variable('value'), Node::variable('next')])))->asExpression();
            $transformerType = $attribute->class->methods->get('normalize')->parameters->at(0)->type;
            if (!$this->type->matches($transformerType)) {
                return Node::if($transformerType->compiledAccept(Node::variable('value')), $node);
            }
            return $node;
        }, $this->transformerAttributes), Node::return(Node::variable('next')->call())];
        return $class->withMethods(Node::method($methodName)->witParameters(Node::parameterDeclaration('value', 'mixed'), Node::parameterDeclaration('references', WeakMap::class))->withReturnType('mixed')->withBody(...$nodes));
    }
    /**
     * @return non-empty-string
     */
    private function methodName(): string
    {
        $slug = preg_replace('/[^a-z0-9]+/', '_', strtolower($this->type->toString()));
        return "transform_{$slug}_" . sha1(serialize([$this->type->toString(), $this->transformerAttributes]));
    }
}