<?php


namespace AutoAdmin\Bundle\Admin\EventListener\Form;


use AutoAdmin\Bundle\Content\Model\SequencedInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;

class CollectionOrderListener implements EventSubscriberInterface
{
    protected array $postedKeysOrder = [];

    public static function getSubscribedEvents(): array
    {
        return [
            FormEvents::PRE_SUBMIT => ['onPreSubmit', 0],
            FormEvents::SUBMIT => ['onSubmit', 0],
        ];
    }

    public function onPreSubmit(FormEvent $event)
    {
        $data = $event->getData();
        $form = $event->getForm();
        $this->postedKeysOrder[spl_object_id($form)] = array_keys($data ?? []);
    }

    public function onSubmit(FormEvent $event)
    {
        $form = $event->getForm();
        $data = $event->getData();

        if (
            empty($data)
            || empty($this->postedKeysOrder[spl_object_id($form)])
        ) {
            return;
        }

        $unorderedKeys = $this->postedKeysOrder[spl_object_id($form)];
        $orderedKeys = $unorderedKeys;
        sort($orderedKeys);
        $orderedKeys = array_combine($orderedKeys, $unorderedKeys);

        // sort $data using $keysOrder
        // recreate form elements and reindex data to comply to post order
        $sortedData = [];
        $sortedForms = [];
        foreach ($orderedKeys as $key => $oldKey) {
            if (!$form->has($oldKey)) {
                continue;
            }
            $sortedData[] = $data[$oldKey];// prepare sorted data for serialized arrays
            $sortedForms[] = [
                $oldKey,
                $form->getConfig()->getOption('entry_type'),
                $form->getConfig()->getOption('entry_options') ?? [],
            ];
            $form->remove($oldKey);
        }

        foreach ($sortedForms as $key => $sortedForm) {
            $form->add($key, $sortedForm[1], $sortedForm[2]);
        }

        if ($data instanceof Collection || $this->isObjectArray($data)) {
            // try to re-sequence objects if SequencedInterface
            $this->doSequenceItems($sortedData);
            if ($data instanceof Collection) {
                $sortedData = new ArrayCollection($sortedData);
            }
            $event->setData($sortedData);
        } elseif ($this->isScalarArray($data)) {
            $event->setData($sortedData);
        }
    }

    private function isObjectArray($subject): bool
    {
        if (!is_array($subject)) {
            return false;
        }

        $isObjectArray = true;

        foreach ($subject as $item) {
            if (($isObjectArray = ($isObjectArray && is_object($item))) === false) {
                break;
            }
        }

        return $isObjectArray;
    }

    private function doSequenceItems($data)
    {
        foreach ($data as $key => $datum) {
            if ($datum instanceof SequencedInterface) {
                $datum->setSequence((int)$key);
            }
        }
    }

    private function isScalarArray($subject): bool
    {
        if (!is_array($subject)) {
            return false;
        }

        $isScalarArray = true;

        foreach ($subject as $item) {
            if (($isScalarArray = ($isScalarArray && is_scalar($item))) === false) {
                break;
            }
        }

        return $isScalarArray;
    }
}
