<?php
/**
 * @package   Essentials YOOtheme Pro 2.2.16 build 0813.1608
 * @author    ZOOlanders https://www.zoolanders.com
 * @copyright Copyright (C) Joolanders, SL
 * @license   http://www.gnu.org/licenses/gpl.html GNU/GPL
 */

namespace ZOOlanders\YOOessentials\Dynamic;

use function YOOtheme\app;
use YOOtheme\Str;
use YOOtheme\Builder\Source;
use ZOOlanders\YOOessentials\Builder\SourceResolver;

class DynamicResolver extends SourceResolver
{
    public const GLOBAL = '~global';

    public static function getSourceTypeDefinitions(Source $source): array
    {
        $reflection = new \ReflectionClass($source);
        $property = $reflection->getProperty('types');
        $property->setAccessible(true);

        return $property->getValue($source);
    }

    private function globalQueries(): array
    {
        return $this->config->get('dynamic.queries', []);
    }

    public function resolveQueryArguments(object $node, array $params = []): void
    {
        if (isset($node->source->query->arguments_extended)) {
            $this->_resolveQueryArguments($node->source->query, $node, $params);
        }

        if (isset($node->source->query->field->arguments_extended)) {
            $this->_resolveQueryFieldArguments($node->source->query, $node, $params);
        }

        foreach ($node->source_extended->props ?? [] as $prop) {
            if (isset($prop->query->arguments_extended)) {
                $this->_resolveQueryArguments($prop->query, $node, $params);
            }

            if (isset($prop->query->field->arguments_extended)) {
                $this->_resolveQueryFieldArguments($prop->query, $node, $params);
            }
        }
    }

    protected function _resolveQueryArguments(object $query, object $node, array $params = []): void
    {
        $argsTypes = $this->_findQueryArgs($query->name);

        $data = $this->_resolveArguments((array) ($query->arguments_extended ?? []), $argsTypes, $node, $params);

        // ensure arguments is an object
        $query->arguments = (object) ($query->arguments ?? []);

        foreach ($data as $key => $value) {
            $query->arguments->{$key} = $value;
        }

        unset($query->arguments_extended);
    }

    protected function _resolveQueryFieldArguments(object $query, object $node, array $params = []): void
    {
        $argsTypes = $this->_findQueryArgs("{$query->name}.{$query->field->name}");
        $data = $this->_resolveArguments((array) ($query->field->arguments_extended ?? []), $argsTypes, $node, $params);

        // ensure arguments is an object
        $query->field->arguments = (object) $query->field->arguments;

        foreach ($data as $key => $value) {
            $query->field->arguments->{$key} = $value;
        }

        unset($query->field->arguments_extended);
    }

    protected function _findQueryArgs(string $name)
    {
        $schema = app(Source::class)->getSchema();
        $fields = $schema->getQueryType()->getFields();

        $path = explode('.', $name);

        if (count($path) === 2) {
            $fields = (array) $fields[$path[0]]->config['type']->config['fields']()[$path[1]];
        } else {
            $fields = (array) ($fields[$name] ?? []);
        }

        return $fields['args'] ?? $fields['config']['args'] ?? [];
    }

    protected function _resolveArguments(array $args, array $argsTypes, object $node, array $params = []): array
    {
        $data = $this->_resolveProps($args, $node, $params);

        foreach ($data as $key => $value) {
            $argType = $argsTypes[$key]['type']->name ?? null;

            // enforce type
            if ($argType === 'Int') {
                $value = (int) $value;
            } elseif ($argType === 'String') {
                $value = (string) $value;
            }

            $data[$key] = $value;
        }

        return $data;
    }

    public function resolveGlobalQueries(object $node): void
    {
        $this->resolveNodeGlobalQuery($node);
        $this->resolvePropsGlobalQuery($node);
    }

    public function resolveNodeGlobalQuery(object $node): void
    {
        $query = $node->source->query->name ?? '';

        if (Str::startsWith($query, self::GLOBAL)) {
            $node->source->query = self::resolveGlobalQuery($query, $this->globalQueries());
        }
    }

    public function resolvePropsGlobalQuery(object $node): void
    {
        $props = $node->source_extended->props ?? [];

        foreach ($props as $prop) {
            $query = $prop->query->name ?? '';

            if (!Str::startsWith($query, self::GLOBAL)) {
                continue;
            }

            $prop->query = self::resolveGlobalQuery($query, $this->globalQueries());
        }
    }

    protected static function resolveGlobalQuery(string $query, $queries = []): ?object
    {
        $id = str_replace(self::GLOBAL . ':', '', $query);
        $key = array_search($id, array_column($queries, 'id'));

        if (isset($queries[$key])) {
            return json_decode(json_encode($queries[$key]['source']['query'] ?? []));
        }

        return null;
    }

    protected static function getGlobalQueryKey(string $id): string
    {
        return self::GLOBAL . ":$id)";
    }
}
