<?php

declare(strict_types=1);

namespace core\handler;

use ReflectionClass;
use ReflectionProperty;
use utils\Command;
use utils\CommandInterface;
use utils\TypesHandler;
use utils\type\CommandType;
use utils\RuntimeName;

final class PassByValueHandler extends AbstractHandler
{
    private const REQUIRED_ARGUMENTS_COUNT = 1;

    /**
     * @return mixed
     */
    public function process(CommandInterface $command)
    {
        $this->validate($command, self::REQUIRED_ARGUMENTS_COUNT, self::class);

        $instance = $command->getPayload()[0];
        return $this->projectValue($instance, $command);
    }

    /**
     * @param mixed $value
     * @return mixed
     */
    private function projectValue($value, CommandInterface $contextCommand)
    {
        if (TypesHandler::isSimpleType($value)) {
            return $value;
        }

        if (is_array($value)) {
            $elements = [];
            foreach ($value as $item) {
                $elements[] = $this->projectValue($item, $contextCommand);
            }

            return new Command(
                RuntimeName::PHP(),
                CommandType::ARRAY(),
                $elements
            );
        }

        return $this->projectObject($value, $contextCommand);
    }

    private function projectObject(object $instance, CommandInterface $contextCommand): CommandInterface
    {
        $typeName = (new ReflectionClass($instance))->getName();
        $propertyNames = [];

        $reflection = new ReflectionClass($instance);
        foreach ($reflection->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
            if ($property->isStatic()) {
                continue;
            }

            $propertyName = $property->getName();
            if (!in_array($propertyName, $propertyNames, true)) {
                $propertyNames[] = $propertyName;
            }
        }

        $getTypeCommand = new Command(
            RuntimeName::PHP(),
            CommandType::GET_TYPE(),
            $typeName
        );
        $currentChain = new Command(
            RuntimeName::PHP(),
            CommandType::CREATE_INSTANCE(),
            $getTypeCommand
        );

        $getInstanceFieldHandler = new GetInstanceFieldHandler();
        foreach ($propertyNames as $propertyName) {
            $propertyValue = $getInstanceFieldHandler->process(
                new Command(
                    RuntimeName::PHP(),
                    CommandType::GET_INSTANCE_FIELD(),
                    [$instance, $propertyName]
                )
            );
            $projectedValue = $this->projectValue($propertyValue, $contextCommand);

            $currentChain = new Command(
                RuntimeName::PHP(),
                CommandType::SET_INSTANCE_FIELD(),
                [$currentChain, $propertyName, $projectedValue]
            );
        }
        return $currentChain;
    }
}
