<?php

namespace KupShop\FeedGeneratorBundle\Utils;

use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlockFactory;

trait ObjectInfoTrait
{
    /**
     * @throws \ReflectionException
     *
     * @private
     */
    public function getFieldsAndMethods(): array
    {
        $introspection = [];

        $f = new \ReflectionClass($this);

        $docBlockFactory = DocBlockFactory::createInstance();

        $ignoredClasses = [
            \KupShop\FeedsBundle\Wrapper\BaseWrapper::class,
            \KupShop\FeedsBundle\Wrapper\MultiWrapper::class,
            \KupShop\FeedsBundle\Wrapper\ObjectMultiWrapper::class,
        ];
        foreach ($f->getMethods() as $method) {
            if (array_search($method->class, $ignoredClasses) !== false || !$method->isPublic() || $method->isConstructor()) {
                continue;
            }

            $description = '';
            $help = '';

            if ($method->getDocComment()) {
                $docBlock = $docBlockFactory->create($method);

                if ($docBlock->getTagsByName('private') || $docBlock->getTagsByName('deprecated') || $this->requiredModuleIsDisabled($docBlock)) {
                    continue;
                }
                $description = $docBlock->getSummary();
                $help = $docBlock->getDescription()->render();
            }

            if (substr($method->name, 0, 6) == 'field_') {
                $name = substr($method->name, 6);
                $rawName = $name;
                $isMethod = false;
            } else {
                $name = $this->printMethodPrototype($method);
                $rawName = $method->name.'()';
                $isMethod = true;
            }

            $return = $method->getReturnType();
            if ($return) {
                $return = strval($return->getName());
            }

            $tmpArr = [
                'name' => $name,
                'rawName' => $rawName,
                'description' => $description,
                'help' => $help,
                'method' => $isMethod,
                'return' => $return,
                'value' => !$isMethod && (!isset($docBlock) || !$docBlock->getTagsByName('implementedInJS')) ? call_user_func([$this, $method->name]) : '',
            ];
            // TODO: remove clone once non shared wrappers are used
            if ($tmpArr['value'] instanceof IObjectInfo) {
                $tmpArr['value'] = clone $tmpArr['value'];
            }
            $introspection[] = $tmpArr;
        }

        usort($introspection, function ($a, $b) {
            return strcmp($a['name'], $b['name']);
        });

        return $introspection;
    }

    protected function printMethodPrototype(\ReflectionMethod $method)
    {
        $args = [];
        foreach ($method->getParameters() as $p) {
            $args[] = ($this->declaresArray($p) ? 'array ' : ($p->getType() !== null && $p->getType()->__toString() ? $p->getType()->__toString().' ' : ''))
                .($p->isPassedByReference() ? '&' : '').'$'.$p->name
                .($p->isOptional() ? ' = '.var_export($p->getDefaultValue(), true) : '');
        }

        return "{$method->name} (".implode(', ', $args).')';
    }

    protected function declaresArray(\ReflectionParameter $reflectionParameter): bool
    {
        $reflectionType = $reflectionParameter->getType();

        if (!$reflectionType) {
            return false;
        }

        $types = $reflectionType instanceof \ReflectionUnionType
            ? $reflectionType->getTypes()
            : [$reflectionType];

        return in_array('array', array_map(fn (\ReflectionNamedType $t) => $t->getName(), $types));
    }

    protected function requiredModuleIsDisabled(DocBlock $docBlock): bool
    {
        $moduleRaw = $docBlock->getTagsByName('module');
        if (!empty($moduleRaw)) {
            $key = trim((string) ($moduleRaw[0] ?? ''), "\"'");
            $parts = explode('__', $key);
            $module = $parts[0];
            $submodule = getVal(1, $parts);
            if (!\Modules::check($module, $submodule)) {
                return true; // disabled module
            }
        }

        return false;
    }
}
