<?php
declare(strict_types=1);

/**
 * This source file is available under the terms of the
 * Pimcore Open Core License (POCL)
 * Full copyright and license information is available in
 * LICENSE.md which is distributed with this source code.
 *
 *  @copyright  Copyright (c) Pimcore GmbH (https://www.pimcore.com)
 *  @license    Pimcore Open Core License (POCL)
 */

namespace Pimcore\Bundle\AdminBundle\Controller\Admin\DataObject;

use Pimcore\Bundle\AdminBundle\Event\AdminEvents;
use Pimcore\Bundle\AdminBundle\Helper\GridHelperService;
use Pimcore\Bundle\AdminBundle\Service\GridData;
use Pimcore\Localization\LocaleServiceInterface;
use Pimcore\Logger;
use Pimcore\Model\DataObject;
use Pimcore\Tool;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

/**
 * @internal
 */
trait DataObjectActionsTrait
{
    protected function renameObject(?DataObject $object, string $key): array
    {
        try {
            if (!$object instanceof DataObject) {
                throw new \Exception('No Object found for given id.');
            }

            $object->setKey($key);
            $object->save();

            return ['success' => true];
        } catch (\Exception $e) {
            Logger::error((string) $e);

            return ['success' => false, 'message' => $e->getMessage()];
        }
    }

    protected function gridProxy(
        array $allParams,
        string $objectType,
        Request $request,
        EventDispatcherInterface $eventDispatcher,
        GridHelperService $gridHelperService,
        LocaleServiceInterface $localeService
    ): array {
        $action = $allParams['xaction'] ?? 'list';
        $csvMode = $allParams['csvMode'] ?? false;

        $requestedLanguage = $allParams['language'] ?? null;
        if ($requestedLanguage) {
            if ($requestedLanguage != 'default') {
                $request->setLocale($requestedLanguage);
            }
        } else {
            $requestedLanguage = $request->getLocale();
        }

        if ($action === 'update') {
            try {
                $data = $this->decodeJson($allParams['data']);
                $object = DataObject::getById($data['id']);

                if (!$object instanceof DataObject\Concrete) {
                    throw $this->createNotFoundException('Object not found');
                }

                if (!$object->isAllowed('publish')) {
                    throw $this->createAccessDeniedException("Permission denied. You don't have the rights to save this object.");
                }

                $objectData = $this->prepareObjectData($data, $object, $requestedLanguage, $localeService);
                $object->setValues($objectData);

                if ($object->getPublished() == false) {
                    $object->setOmitMandatoryCheck(true);
                }
                $object->save();

                return [
                    'success' => true,
                    'data' => GridData\DataObject::getData($object, $allParams['fields'], $requestedLanguage),
                ];
            } catch (\Exception $e) {
                return [
                    'success' => false,
                    'message' => $e->getMessage(),
                ];
            }
        } else { // get list of objects/variants
            $list = $gridHelperService->prepareListingForGrid($allParams, $requestedLanguage, $this->getAdminUser());

            if ($objectType === DataObject::OBJECT_TYPE_OBJECT) {
                $beforeListLoadEvent = new GenericEvent($this, [
                    'list' => $list,
                    'context' => $allParams,
                ]);
                $eventDispatcher->dispatch($beforeListLoadEvent, AdminEvents::OBJECT_LIST_BEFORE_LIST_LOAD);
                /** @var DataObject\Listing\Concrete $list */
                $list = $beforeListLoadEvent->getArgument('list');
            }

            if ($objectType === DataObject::OBJECT_TYPE_VARIANT) {
                $list->setObjectTypes([DataObject::OBJECT_TYPE_VARIANT]);
            }

            $list->load();

            $objects = [];
            foreach ($list->getObjects() as $object) {
                if ($csvMode) {
                    $o = DataObject\Service::getCsvDataForObject($object, $requestedLanguage, $request->get('fields'), GridData\DataObject::getHelperDefinitions(), $localeService, 'title', false, $allParams['context']);
                    // Like for treeGetChildrenByIdAction, so we respect isAllowed method which can be extended (object DI) for custom permissions, so relying only users_workspaces_object is insufficient and could lead security breach
                    if ($object->isAllowed('list')) {
                        $objects[] = $o;
                    }
                } else {
                    $o = GridData\DataObject::getData($object, $allParams['fields'] ?? null, $requestedLanguage,
                        ['csvMode' => $csvMode]);
                    if ($o['permissions']['list']) {
                        $objects[] = $o;
                    }
                }
            }

            $result = [
                'success' => true,
                'data' => $objects,
                'total' => $list->getTotalCount(),
            ];

            if ($objectType === DataObject::OBJECT_TYPE_OBJECT) {
                $afterListLoadEvent = new GenericEvent($this, [
                    'list' => $result,
                    'context' => $allParams,
                ]);
                $eventDispatcher->dispatch($afterListLoadEvent, AdminEvents::OBJECT_LIST_AFTER_LIST_LOAD);
                $result = $afterListLoadEvent->getArgument('list');
            }

            return $result;
        }
    }

    /**
     * @throws \Exception
     */
    private function prepareObjectData(
        array $data,
        DataObject\Concrete $object,
        string $requestedLanguage,
        LocaleServiceInterface $localeService
    ): array {
        $user = Tool\Admin::getCurrentUser();
        $languagePermissions = [];
        if (!$user->isAdmin()) {
            $languagePermissionsData = $object->getPermissions('lEdit', $user);

            if ($languagePermissionsData['lEdit']) {
                $languagePermissions = explode(',', $languagePermissionsData['lEdit']);
            }
        }

        $class = $object->getClass();
        $objectData = [];
        foreach ($data as $key => $value) {
            $parts = explode('~', $key);
            if (substr($key, 0, 1) == '~') {
                [, $type, $field, $keyId] = $parts;

                if ($type == 'classificationstore') {
                    $groupKeyId = array_map('intval', explode('-', $keyId));
                    [$groupId, $keyId] = $groupKeyId;

                    $getter = 'get' . ucfirst($field);
                    if (method_exists($object, $getter)) {
                        /** @var DataObject\ClassDefinition\Data\Classificationstore $csFieldDefinition */
                        $csFieldDefinition = $object->getClass()->getFieldDefinition($field);
                        $csLanguage = $requestedLanguage;
                        if (!$csFieldDefinition->isLocalized()) {
                            $csLanguage = 'default';
                        }

                        /** @var DataObject\Classificationstore $classificationStoreData */
                        $classificationStoreData = $object->$getter();

                        $keyConfig = DataObject\Classificationstore\KeyConfig::getById($keyId);
                        if ($keyConfig) {
                            $fieldDefinition = DataObject\Classificationstore\Service::getFieldDefinitionFromJson(
                                json_decode($keyConfig->getDefinition(), true),
                                $keyConfig->getType()
                            );
                            if ($fieldDefinition && method_exists($fieldDefinition, 'getDataFromGridEditor')) {
                                $value = $fieldDefinition->getDataFromGridEditor($value, $object, []);
                            }
                        }

                        $object->markFieldDirty($field);

                        $activeGroups = $classificationStoreData->getActiveGroups() ?: [];
                        $activeGroups[$groupId] = true;
                        $classificationStoreData->setActiveGroups($activeGroups);
                        $classificationStoreData->setLocalizedKeyValue($groupId, $keyId, $value, $csLanguage);
                    }
                }
            } elseif (count($parts) > 1) {
                $brickType = $parts[0];
                $brickDescriptor = null;

                if (strpos($brickType, '?') !== false) {
                    $brickDescriptor = substr($brickType, 1);
                    $brickDescriptor = json_decode($brickDescriptor, true);
                    $brickType = $brickDescriptor['containerKey'];
                }
                $brickKey = $parts[1];
                $brickField = DataObject\Service::getFieldForBrickType($object->getClass(), $brickType);

                $fieldGetter = 'get' . ucfirst($brickField);
                $brickGetter = 'get' . ucfirst($brickType);

                $brick = $object->$fieldGetter()->$brickGetter();
                if (empty($brick)) {
                    $classname = '\\Pimcore\\Model\\DataObject\\Objectbrick\\Data\\' . ucfirst($brickType);
                    $brickSetter = 'set' . ucfirst($brickType);
                    $brick = new $classname($object);
                    $object->$fieldGetter()->$brickSetter($brick);
                }

                if ($brickDescriptor) {
                    $brickDefinition = DataObject\Objectbrick\Definition::getByKey($brickType);
                    /** @var DataObject\ClassDefinition\Data\Localizedfields $fieldDefinitionLocalizedFields */
                    $fieldDefinitionLocalizedFields = $brickDefinition->getFieldDefinition('localizedfields');
                    $fieldDefinition = $fieldDefinitionLocalizedFields->getFieldDefinition($brickKey);
                } else {
                    $fieldDefinition = $this->getFieldDefinitionFromBrick($brickType, $brickKey);
                }

                if ($fieldDefinition && method_exists($fieldDefinition, 'getDataFromGridEditor')) {
                    $value = $fieldDefinition->getDataFromGridEditor($value, $object, []);
                }

                if ($brickDescriptor) {
                    /** @var DataObject\Localizedfield $localizedFields */
                    $localizedFields = $brick->getLocalizedfields();
                    $localizedFields->setLocalizedValue($brickKey, $value);
                } else {
                    $brickKeySetter = 'set' . ucfirst($brickKey);
                    $brick->$brickKeySetter($value);
                    $brick->markFieldDirty($brickKey);
                }
            } else {
                if ($languagePermissions) {
                    $fd = $class->getFieldDefinition($key);
                    if (!$fd) {
                        // try to get via localized fields
                        $localized = $class->getFieldDefinition('localizedfields');
                        if ($localized instanceof DataObject\ClassDefinition\Data\Localizedfields) {
                            $field = $localized->getFieldDefinition($key);
                            if ($field) {
                                $currentLocale = $localeService->findLocale();
                                if (!in_array($currentLocale, $languagePermissions)) {
                                    continue;
                                }
                            }
                        }
                    }
                }

                $fieldDefinition = $this->getFieldDefinition($class, $key);
                if ($fieldDefinition && method_exists($fieldDefinition, 'getDataFromGridEditor')) {
                    $value = $fieldDefinition->getDataFromGridEditor($value, $object, []);
                }

                $objectData[$key] = $value;
            }
        }

        return $objectData;
    }

    protected function getFieldDefinition(DataObject\ClassDefinition $class, string $key): ?DataObject\ClassDefinition\Data
    {
        $fieldDefinition = $class->getFieldDefinition($key);
        if ($fieldDefinition) {
            return $fieldDefinition;
        }

        return $fieldDefinition;
    }

    protected function getFieldDefinitionFromBrick(string $brickType, string $key): ?DataObject\ClassDefinition\Data
    {
        $brickDefinition = DataObject\Objectbrick\Definition::getByKey($brickType);
        $fieldDefinition = null;
        if ($brickDefinition) {
            $fieldDefinition = $brickDefinition->getFieldDefinition($key);
        }

        return $fieldDefinition;
    }
}
