<?php

declare(strict_types=1);

namespace Axtiva\FlexibleGraphql\Generator\TypeRegistry\Foundation;

use Axtiva\FlexibleGraphql\Generator\Config\TypeRegistryGeneratorConfigInterface;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use Axtiva\FlexibleGraphql\Generator\TypeRegistry\DirectiveRegistryMethodGeneratorInterface;
use Axtiva\FlexibleGraphql\Generator\TypeRegistry\TypeRegistryGeneratorInterface;
use Axtiva\FlexibleGraphql\Generator\TypeRegistry\TypeRegistryMethodGeneratorInterface;

use function implode;
use function in_array;

use const PHP_EOL;

class TypeRegistryPsrContainerGenerator implements TypeRegistryGeneratorInterface
{
    private ?string $namespace;
    private string $classNameShort;
    private TypeRegistryMethodGeneratorInterface $typeRegistryMethodGenerator;
    private DirectiveRegistryMethodGeneratorInterface $directiveRegistryMethodGenerator;
    private TypeRegistryGeneratorConfigInterface $config;

    public function __construct(
        TypeRegistryGeneratorConfigInterface $config,
        TypeRegistryMethodGeneratorInterface $typeRegistryMethodGenerator,
        DirectiveRegistryMethodGeneratorInterface $directiveRegistryMethodGenerator
    ) {
        $this->config = $config;
        $this->namespace = $config->getTypeRegistryNamespace();
        $this->classNameShort = $config->getTypeRegistryClassName();
        $this->typeRegistryMethodGenerator = $typeRegistryMethodGenerator;
        $this->directiveRegistryMethodGenerator = $directiveRegistryMethodGenerator;
    }

    public function getConfig(): TypeRegistryGeneratorConfigInterface
    {
        return $this->config;
    }

    public function generate(Schema $schema): string
    {
        return sprintf(
        /** @lang text */ '<?php
/**
 * Autogenerated file by axtiva/flexible-graphql-php Do not edit it manually
 */ 
%s

use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use Axtiva\FlexibleGraphql\Type\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\FieldArgument;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\InputObjectField;
use Psr\Container\ContainerInterface;
use GraphQL\Type\Schema;

class %s
{
    private ContainerInterface $container;
    
    /**
     * @var array<string, Type>
     */
    private array $types = [];
    
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function getType(string $name): Type
    {
        return $this->types[$name] ??= $this->{$name}();
    }
    
    %s

}
',
            $this->namespace ? "namespace {$this->namespace};" : '',
            $this->classNameShort,
            $this->getAllMethods($schema)
        );
    }

    private function getAllMethods(Schema $schema): string
    {
        $string = [];
        $hasQuery = false;
        $hasMutation = false;
        $allBuiltInTypes = array_keys(Type::getAllBuiltInTypes());
        foreach ($schema->getTypeMap() as $type) {
            if (!in_array($type->name, $allBuiltInTypes)) {
                $string[] = $this->typeRegistryMethodGenerator->getMethod($type);
                if ($type->name === 'Query') {
                    $hasQuery = true;
                }
                if ($type->name === 'Mutation') {
                    $hasMutation = true;
                }
            }
        }

        if (!$hasQuery) {
            $string[] = <<<'PHP'
    public function Query()
    {
        return new ObjectType(['name' => 'Query']);
    }
PHP;
        }

        if (!$hasMutation) {
            $string[] = <<<'PHP'
    public function Mutation()
    {
        return new ObjectType(['name' => 'Mutation']);
    }
PHP;
        }

        $allBuiltInDirectives = array_keys(Directive::getInternalDirectives());
        $directivesMethods = [];
        foreach ($schema->getDirectives() as $directive) {
            if (in_array($directive->name, $allBuiltInDirectives) === false) {
                $directivesMethods[] = $this->directiveRegistryMethodGenerator->getMethodCall($directive);
                $string[] = $this->directiveRegistryMethodGenerator->getMethod($directive);
            }
        }

        $string[] = sprintf(
            '
    public function getDirectives()
    {
        return [%s];
    }
        ', implode(',', $directivesMethods));

        return implode(PHP_EOL . PHP_EOL, $string);
    }
}