<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */
declare (strict_types=1);
namespace RectorPrefix20210928\Nette\Utils;

use RectorPrefix20210928\Nette;
/**
 * PHP type reflection.
 */
final class Type
{
    /** @var array */
    private $types;
    /** @var bool */
    private $single;
    /** @var string  |, & */
    private $kind;
    /**
     * Creates a Type object based on reflection. Resolves self, static and parent to the actual class name.
     * If the subject has no type, it returns null.
     * @param  \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty  $reflection
     */
    public static function fromReflection($reflection) : ?self
    {
        if ($reflection instanceof \ReflectionProperty && \PHP_VERSION_ID < 70400) {
            return null;
        } elseif ($reflection instanceof \ReflectionMethod) {
            $type = $reflection->getReturnType() ?? (\PHP_VERSION_ID >= 80100 ? $reflection->getTentativeReturnType() : null);
        } else {
            $type = $reflection instanceof \ReflectionFunctionAbstract ? $reflection->getReturnType() : $reflection->getType();
        }
        if ($type === null) {
            return null;
        } elseif ($type instanceof \ReflectionNamedType) {
            $name = self::resolve($type->getName(), $reflection);
            return new self($type->allowsNull() && $type->getName() !== 'mixed' ? [$name, 'null'] : [$name]);
        } elseif ($type instanceof \ReflectionUnionType || $type instanceof \RectorPrefix20210928\ReflectionIntersectionType) {
            return new self(\array_map(function ($t) use($reflection) {
                return self::resolve($t->getName(), $reflection);
            }, $type->getTypes()), $type instanceof \ReflectionUnionType ? '|' : '&');
        } else {
            throw new \RectorPrefix20210928\Nette\InvalidStateException('Unexpected type of ' . \RectorPrefix20210928\Nette\Utils\Reflection::toString($reflection));
        }
    }
    /**
     * Creates the Type object according to the text notation.
     * @param string $type
     */
    public static function fromString($type) : self
    {
        if (!\preg_match('#(?:
			\\?([\\w\\\\]+)|
			[\\w\\\\]+ (?: (&[\\w\\\\]+)* | (\\|[\\w\\\\]+)* )
		)()$#xAD', $type, $m)) {
            throw new \RectorPrefix20210928\Nette\InvalidArgumentException("Invalid type '{$type}'.");
        }
        [, $nType, $iType] = $m;
        if ($nType) {
            return new self([$nType, 'null']);
        } elseif ($iType) {
            return new self(\explode('&', $type), '&');
        } else {
            return new self(\explode('|', $type));
        }
    }
    /**
     * Resolves 'self', 'static' and 'parent' to the actual class name.
     * @param  \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty  $reflection
     * @param string $type
     */
    public static function resolve($type, $reflection) : string
    {
        $lower = \strtolower($type);
        if ($reflection instanceof \ReflectionFunction) {
            return $type;
        } elseif ($lower === 'self' || $lower === 'static') {
            return $reflection->getDeclaringClass()->name;
        } elseif ($lower === 'parent' && $reflection->getDeclaringClass()->getParentClass()) {
            return $reflection->getDeclaringClass()->getParentClass()->name;
        } else {
            return $type;
        }
    }
    private function __construct(array $types, string $kind = '|')
    {
        if ($types[0] === 'null') {
            // null as last
            \array_push($types, \array_shift($types));
        }
        $this->types = $types;
        $this->single = ($types[1] ?? 'null') === 'null';
        $this->kind = \count($types) > 1 ? $kind : '';
    }
    public function __toString() : string
    {
        return $this->single ? (\count($this->types) > 1 ? '?' : '') . $this->types[0] : \implode($this->kind, $this->types);
    }
    /**
     * Returns the array of subtypes that make up the compound type as strings.
     * @return string[]
     */
    public function getNames() : array
    {
        return $this->types;
    }
    /**
     * Returns the array of subtypes that make up the compound type as Type objects:
     * @return self[]
     */
    public function getTypes() : array
    {
        return \array_map(function ($name) {
            return self::fromString($name);
        }, $this->types);
    }
    /**
     * Returns the type name for single types, otherwise null.
     */
    public function getSingleName() : ?string
    {
        return $this->single ? $this->types[0] : null;
    }
    /**
     * Returns true whether it is a union type.
     */
    public function isUnion() : bool
    {
        return $this->kind === '|';
    }
    /**
     * Returns true whether it is an intersection type.
     */
    public function isIntersection() : bool
    {
        return $this->kind === '&';
    }
    /**
     * Returns true whether it is a single type. Simple nullable types are also considered to be single types.
     */
    public function isSingle() : bool
    {
        return $this->single;
    }
    /**
     * Returns true whether the type is both a single and a PHP built-in type.
     */
    public function isBuiltin() : bool
    {
        return $this->single && \RectorPrefix20210928\Nette\Utils\Reflection::isBuiltinType($this->types[0]);
    }
    /**
     * Returns true whether the type is both a single and a class name.
     */
    public function isClass() : bool
    {
        return $this->single && !\RectorPrefix20210928\Nette\Utils\Reflection::isBuiltinType($this->types[0]);
    }
    /**
     * Verifies type compatibility. For example, it checks if a value of a certain type could be passed as a parameter.
     * @param string $type
     */
    public function allows($type) : bool
    {
        if ($this->types === ['mixed']) {
            return \true;
        }
        $type = self::fromString($type);
        if ($this->isIntersection()) {
            if (!$type->isIntersection()) {
                return \false;
            }
            return \RectorPrefix20210928\Nette\Utils\Arrays::every($this->types, function ($currentType) use($type) {
                $builtin = \RectorPrefix20210928\Nette\Utils\Reflection::isBuiltinType($currentType);
                return \RectorPrefix20210928\Nette\Utils\Arrays::some($type->types, function ($testedType) use($currentType, $builtin) {
                    return $builtin ? \strcasecmp($currentType, $testedType) === 0 : \is_a($testedType, $currentType, \true);
                });
            });
        }
        $method = $type->isIntersection() ? 'some' : 'every';
        return \RectorPrefix20210928\Nette\Utils\Arrays::$method($type->types, function ($testedType) {
            $builtin = \RectorPrefix20210928\Nette\Utils\Reflection::isBuiltinType($testedType);
            return \RectorPrefix20210928\Nette\Utils\Arrays::some($this->types, function ($currentType) use($testedType, $builtin) {
                return $builtin ? \strcasecmp($currentType, $testedType) === 0 : \is_a($testedType, $currentType, \true);
            });
        });
    }
}
