<?php


namespace Mainto\RpcServer\RpcServer\Definition\RpcObject;


use Doctrine\Common\Annotations\AnnotationReader;
use Illuminate\Support\Str;
use Mainto\RpcServer\RpcAnnotations\RpcComment;
use Mainto\RpcServer\RpcAnnotations\RpcValidation;
use Mainto\RpcServer\RpcServer\Definition\Property;
use Mainto\RpcServer\RpcServer\Definition\Traits\ValidateGetter;
use Mainto\RpcServer\RpcServer\RpcDefinition;
use Mainto\RpcServer\RpcServer\RpcObject;
use Mainto\RpcServer\Util\ObjectMapper\ClassRef;
use ReflectionException;
use ReflectionMethod;
use ReflectionProperty;

class ObjectRef extends ClassRef {
    use ValidateGetter;

    /**
     * @var array
     */
    private static array $classRefMap;

    /**
     * @param $object
     * @return ObjectRef
     * @throws ReflectionException
     */
    public static function getRef ($object): ObjectRef {
        $objectKey = is_object($object) ? get_class($object) : $object;
        if (isset(static::$classRefMap[$objectKey])) {
            return static::$classRefMap[$objectKey];
        } else {
            return static::$classRefMap[$objectKey] = new static($object);
        }
    }

    public function loadProperties () {
        $methods = array_flip(array_map(fn($method) => $method->name, $this->class->getMethods(ReflectionMethod::IS_PUBLIC)));

        // todo in php8 could use https://www.php.net/manual/en/reflectionproperty.getdefaultvalue.php
        $defaultProperties = $this->class->getDefaultProperties();

        $this->properties = array_map(function (ReflectionProperty $property) use ($methods, $defaultProperties) {
            $setMethod = 'set'.Str::studly($property->name);

            $definitionProperty = new Property();
            $definitionProperty->name = $property->name;
            $definitionProperty->type = $this->getType($property, $this);
            if (is_subclass_of($definitionProperty->type, RpcObject::class)) {
                if (!RpcDefinition::getInstance()->inStructCache($definitionProperty->type)) {
                    $_ = RpcDefinition::getInstance()->objectStruct($definitionProperty->type);
                }
            }

            if (optional($property->getType())->allowsNull()) {
                $definitionProperty->nullable = true;
            }

            foreach (app(AnnotationReader::class)->getPropertyAnnotations($property) as $annotation) {
                if ($annotation instanceof RpcComment) {
                    $definitionProperty->comment = $annotation->comment;
                } elseif ($annotation instanceof RpcValidation) {
                    $definitionProperty->validation = $annotation->validation;
                }
            }

            if (array_key_exists($property->name, $defaultProperties)) {
                $definitionProperty->default = $defaultProperties[$property->name];
                $definitionProperty->defaultValueAvailable = true;
            } else {
                $definitionProperty->require = true;
            }

            $definitionProperty->setterMethod = isset($methods[$setMethod]) ? $setMethod : '';

            return $definitionProperty;
        }, $this->class->getProperties(ReflectionProperty::IS_PUBLIC));
    }
}