<?php

namespace Velis\Dto\Utility;

use ReflectionAttribute;
use ReflectionClass;
use ReflectionException;
use ReflectionNamedType;
use ReflectionProperty;
use ReflectionUnionType;
use Velis\Dto\BaseDto;

/**
 * Utility methods for the ReflectionProperty handling purposes.
 *
 * @author Szymon Janaczek <szymon.janaczek@velistech.com>
 */
class PropertyUtility
{
    /**
     * @param class-string|object $objectOrClass
     * @return array<ReflectionProperty>
     * @throws ReflectionException
     */
    public static function getPublicProperties(string|object $objectOrClass): array
    {
        $reflectionClass = new ReflectionClass($objectOrClass);

        return $reflectionClass->getProperties(ReflectionProperty::IS_PUBLIC);
    }

    /**
     * Check whether a given property has the given type.
     */
    public static function hasType(ReflectionProperty $property, $type): bool
    {
        if ($property->getType() instanceof ReflectionUnionType) {
            return count(array_filter(
                $property->getType()->getTypes(),
                fn (ReflectionNamedType $namedType) => $namedType->getName() == $type
            )) > 0;
        }

        return $property->getType()?->getName() == $type;
    }

    /**
     * Check whether given property is optional (has @see Optional) type.
     */
    public static function isOptional(ReflectionProperty $property): bool
    {
        return self::hasType($property, Optional::class);
    }

    /**
     * Checks whether given property has the type that extends BaseDTO type.
     * If so, return its class string, otherwise return false.
     * For union type it returns array of class strings.
     *
     * @return array<class-string<BaseDto>>|false
     */
    public static function isDto(ReflectionProperty $property): array|false
    {
        if ($property->getType() instanceof ReflectionUnionType) {
            $types = [];
            for ($i = 0; $i < count($property->getType()->getTypes()); $i++) {
                $type = $property->getType()->getTypes()[$i];
                if (is_subclass_of($type->getName(), BaseDto::class)) {
                    $types[] = $type->getName();
                }
            }
            return empty($types) ? false : $types;
        }

        if ($property->hasType() && is_subclass_of($property->getType()->getName(), BaseDto::class)) {
            return [$property->getType()->getName()];
        }

        return false;
    }

    /**
     * @param class-string $attribute
     */
    public static function hasAttribute(ReflectionProperty $property, string $attribute): bool
    {
        $found = $property->getAttributes($attribute, ReflectionAttribute::IS_INSTANCEOF);

        return (count($found) > 0);
    }
}
