<?php

namespace AutoAdmin\Bundle\BrickBuilder\Storage;

use AutoAdmin\Bundle\BrickBuilder\Brick\BrickConfigManager;
use AutoAdmin\Bundle\BrickBuilder\Brick\Config\BrickConfiguration;
use AutoAdmin\Bundle\BrickBuilder\Brick\ConfigSetting\EntityCollectionSetting;
use AutoAdmin\Bundle\BrickBuilder\Brick\ConfigSetting\EntitySetting;
use AutoAdmin\Bundle\BrickBuilder\Brick\ConfigSetting\ScalarCollectionSetting;
use AutoAdmin\Bundle\BrickBuilder\Brick\ConfigSetting\ScalarSetting;
use AutoAdmin\Bundle\BrickBuilder\Brick\ConfigSetting\SubEntityCollectionSetting;
use AutoAdmin\Bundle\BrickBuilder\Exception\BadSettingFormatException;
use AutoAdmin\Bundle\BrickBuilder\Model\BrickInterface;
use AutoAdmin\Bundle\Content\Model\SequencedInterface;
use AutoAdmin\Bundle\Core\Doctrine\ListenerStateManager;
use Doctrine\ORM\EntityManagerInterface;
use RuntimeException;

class BrickStorage implements BrickStorageInterface
{
    public function __construct(
        protected BrickConfigManager $configManager,
        protected EntityManagerInterface $em,
        protected ObjectSettingFormat $brickFormat,
        protected ListenerStateManager $listenerStateManager,
    ) {
    }

    public function fromDatabaseFormat(BrickInterface $brick): void
    {
        /** @var BrickConfiguration $configuration */
        $configuration = $this->configManager->getHandlerConfiguration($brick->getType());
        $settings = $brick->getDbSettings();

        foreach ($settings as $key => $val) {
            if (!array_key_exists($key, $settingsConfigs = $configuration->getSettings())) {
                continue;
            }

            $settingConfig = $settingsConfigs[$key];

            switch ($settingConfig->getType()) {
                case EntitySetting::class:
                    if ($val !== null && !is_string($val)) {
                        throw new BadSettingFormatException($brick, $key);
                    }
                    $settings[$key] = $this->brickFormat->stringToObject($val);

                    break;
                case EntityCollectionSetting::class:
                case SubEntityCollectionSetting::class:
                    foreach ($val as $index => $obj) {
                        if (!is_string($obj)) {
                            throw new BadSettingFormatException($brick, $key);
                        }
                        $settings[$key][$index] = $this->brickFormat->stringToObject($obj);
                    }
                    break;
                case ScalarSetting::class:
                case ScalarCollectionSetting::class:
                default:
                    break;
            }
        }

        $brick->setSettingsNoUpdateFlag($settings);
    }

    public function toDatabaseFormat(BrickInterface $brick): void
    {
        /** @var BrickConfiguration $configuration */
        $configuration = $this->configManager->getHandlerConfiguration($brick->getType());

        $settings = $brick->getSettings();

        foreach ($settings as $key => $val) {
            if (!array_key_exists($key, $settingsConfigs = $configuration->getSettings())) {
                continue;
            }

            $settingConfig = $settingsConfigs[$key];

            switch ($settingConfig->getType()) {
                case EntitySetting::class:
                    if (is_object($val)) {
                        $settings[$key] = $this->brickFormat->objectToString($val);
                    }
                    break;
                case EntityCollectionSetting::class:
                case SubEntityCollectionSetting::class:

                    /*
                     * If already string-format, we don't need to transform it
                     */
                    foreach ($val as $index => $obj) {
                        if (!is_string($obj) && !is_object($obj)) {
                            throw new RuntimeException(
                                'Setting encountered during save was neither object nor string.'
                            );
                        }

                        $converted = is_object($obj) ? $this->brickFormat->objectToString($obj) : $obj;
                        $settings[$key][$index] = $converted;
                    }
                    $settings[$key] = array_filter(array_values($settings[$key]));// reindex

                    // try to order items following their sequence
                    uasort(
                        $settings[$key],
                        function ($a, $b) {
                            if (
                                !$a instanceof SequencedInterface
                                || !$b instanceof SequencedInterface
                            ) {
                                return 0;
                            }

                            return $a->getSequence() < $b->getSequence();
                        }
                    );
                    break;
                case ScalarCollectionSetting::class:
                    $settings[$key] = array_filter(array_values($val));// reindex
                    break;
                case ScalarSetting::class:
                default:
                    break;
            }

            // mark brick setting index as transformed to avoid reprocessing if multiple flush are occurring
            //$this->trackedBricks[spl_object_hash($brick)][$key] = [$val, $settings[$key]];
        }

        $brick->setDbSettings($settings);
    }
}
