<?php

namespace AutoAdmin\Bundle\Content\Service;

use AutoAdmin\Bundle\Content\Duplicator\DuplicatorInterface;
use Doctrine\Common\Util\ClassUtils;
use LogicException;

class DuplicatorService
{
    /**
     * @var DuplicatorInterface[]|array
     */
    protected array $duplicators = [];

    public function duplicate($entity, $parent = null): ?object
    {
        if ($entity == null) {
            return null;
        }

        $class = ClassUtils::getRealClass(get_class($entity));
        $duplicate = new $class();

        foreach ($this->duplicators as $duplicator) {
            if (!$duplicator->isSupported($entity)) {
                continue;
            }

            $duplicate = $duplicator->duplicate($entity, $duplicate, $parent);
        }

        $duplicate = $this->dumbDuplicate($entity, $duplicate);

        return $duplicate;
    }

    public function addDuplicator($definition): void
    {
        $class = get_class($definition);

        if (isset($this->duplicators[$class])) {
            throw new LogicException(
                sprintf(
                    'Duplicator "%s" already exists',
                    $class
                )
            );
        }

        $this->duplicators[$class] = $definition;
    }

    /**
     * Dumb data copy for builtin types (example: avoid nullable in a bool prop)
     *
     * @param object $entity
     * @param object $duplicate
     * @return object
     */
    protected function dumbDuplicate(object $entity, object $duplicate): object
    {
        $refl = new \ReflectionClass($entity);

        foreach ($refl->getProperties() as $property) {
            $propertyType = $property->getType();

            if (is_null($propertyType)) {
                continue;
            }

            $types = [];
            if ($propertyType instanceof \ReflectionNamedType) {
                $types = [$propertyType];
            } elseif ($propertyType instanceof \ReflectionUnionType || $propertyType instanceof \ReflectionIntersectionType) {
                $types = $propertyType->getTypes();
            }

            foreach ($types as $type) {
                if ($type?->isBuiltin()) {
                    switch ($type->getName()) {
                        case 'bool':
                            $currentVal = $property->getValue($duplicate);
                            if ($currentVal !== false && $currentVal !== true) {
                                $currentVal = $property->getValue($entity);
                                $property->setValue($duplicate, $currentVal);
                            }
                            break;
                        default:
                            // do nothing
                            break;
                    }
                }
            }
        }

        return $duplicate;
    }
}
