<?php

namespace AutoAdmin\Bundle\BrickBuilder\Builder;

use AutoAdmin\Bundle\BrickBuilder\Entity\Brick;
use AutoAdmin\Bundle\BrickBuilder\Model\AbstractBrickSubContent;
use AutoAdmin\Bundle\BrickBuilder\Model\BrickInterface;
use AutoAdmin\Bundle\Content\Service\DuplicatorService;
use AutoAdmin\Bundle\Media\Model\ImageInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use Exception;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\PropertyAccess\PropertyAccessor;

class BuilderService
{
    private PropertyAccessor $accessor;
    private FormFactoryInterface $factory;
    private DuplicatorService $duplicatorService;
    private EntityManagerInterface $manager;

    public function __construct(
        PropertyAccessor $accessor,
        FormFactoryInterface $factory,
        DuplicatorService $duplicatorService,
        ManagerRegistry $doctrine
    ) {
        $this->accessor = $accessor;
        $this->factory = $factory;
        $this->duplicatorService = $duplicatorService;
        $this->manager = $doctrine->getManager();
    }

    public function createRoot()
    {
        return (new Brick())->setType('brick.root');
    }

    public function getRoot(object $entity, string $property, bool $create = false)
    {
        $root = $this->accessor->getValue($entity, $property);
        if ($root === null && $create === true) {
            $root = $this->createRoot();
        }

        return $root;
    }

    public function buildForm($subject = null): FormBuilderInterface
    {
        $fb = $this->factory->createBuilder(FormType::class, $subject, ['data_class' => Brick::class,]);
        $fb->add('type', ChoiceType::class);
        $fb->add('id', HiddenType::class);

        return $fb;
    }

    public function reorder(BrickInterface $brick, int $position): BrickInterface
    {
        $parentBrick = $brick->getParent();
        $children = $parentBrick->getChildren();
        if ($children->contains($brick)) {
            $children->remove($children->indexOf($brick));
        }
        $childrenArray = $children->toArray();
        array_splice($childrenArray, $position, 0, [$brick]);
        $children = new ArrayCollection($childrenArray);

        $i = -1;
        /** @var BrickInterface $child */
        foreach ($children as $child) {
            $child->setPosition(++$i);
        }

        $parentBrick->setChildren($children);

        return $brick;
    }

    public function replicate(BrickInterface $originalBrick, BrickInterface $parent = null): Brick
    {
        $brick = new Brick();
        $brick->setType($originalBrick->getType());
        $brick->setParent($parent ?? $originalBrick->getParent());
        $brick->setEnabled($originalBrick->isEnabled());

        $settings = $originalBrick->getSettings();
        foreach ($settings as $key => $setting) {
            if ($key == 'image' && is_string($setting)) {
                $setting = $this->stringToObject($setting);
            }

            if (
                $setting instanceof ImageInterface
                || $setting instanceof AbstractBrickSubContent
            ) {
                $image = $this->duplicatorService->duplicate($setting);

                $this->manager->persist($image);
                $this->manager->flush();
                $this->manager->refresh($image);

                $settings[$key] = $image;
            }

            if (is_array($setting)) {
                foreach ($setting as $idx => $item) {
                    if (
                        $item instanceof ImageInterface
                        || $item instanceof AbstractBrickSubContent
                    ) {
                        $dupItem = $this->duplicatorService->duplicate($item);
                        $this->manager->persist($dupItem);
                        $this->manager->flush();
                        $this->manager->refresh($dupItem);

                        $settings[$key][$idx] = $dupItem;
                    }
                }
            }
        }
        $brick->setSettings($settings);
        $brick->setPosition($originalBrick->getPosition() + 1);

        foreach ($originalBrick->getChildren() as $idx => $child) {
            $dupChild = $this->replicate($child, $brick);
            $dupChild->setPosition($idx);
            $brick->getChildren()->add($dupChild);
        }

        return $brick;
    }

    protected function stringToObject(?string $stringObject)
    {
        if (is_null($stringObject)) {
            return null;
        }

        list($class, $identifier) = explode('#', $stringObject);
        if (empty($class)) {
            throw new Exception('Could not extract class and identifier from "'.$stringObject.'"');
        }
        $repo = $this->manager->getRepository($class);
        $stringObject = $repo->find($identifier);

        return $stringObject;
    }
}
