<?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;

use Pimcore\Bundle\AdminBundle\Controller\AdminAbstractController;
use Pimcore\Bundle\AdminBundle\Event\AdminEvents;
use Pimcore\Model\Element\Tag;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

/**
 * @internal
 */
#[Route('/tags')]
class TagsController extends AdminAbstractController
{
    #[Route('/add', name: 'pimcore_admin_tags_add', methods: ['POST'])]
    public function addAction(Request $request): JsonResponse
    {
        $this->checkPermission('tags_configuration');

        try {
            $tag = new Tag();
            $tag->setName(strip_tags($request->get('text', '')));
            $tag->setParentId((int)$request->get('parentId'));
            $tag->save();

            return $this->adminJson(['success' => true, 'id' => $tag->getId()]);
        } catch (\Exception $e) {
            return $this->adminJson(['success' => false, 'message' => $e->getMessage()]);
        }
    }

    /**
     * @throws \Exception
     */
    #[Route('/delete', name: 'pimcore_admin_tags_delete', methods: ['DELETE'])]
    public function deleteAction(Request $request): JsonResponse
    {
        $this->checkPermission('tags_configuration');

        $tag = Tag::getById((int) $request->get('id'));
        if ($tag) {
            $tag->delete();

            return $this->adminJson(['success' => true]);
        } else {
            throw $this->createNotFoundException('Tag with ID ' . $request->get('id') . ' not found.');
        }
    }

    /**
     * @throws \Exception
     */
    #[Route('/update', name: 'pimcore_admin_tags_update', methods: ['PUT'])]
    public function updateAction(Request $request): JsonResponse
    {
        $this->checkPermission('tags_configuration');

        $tag = Tag::getById((int) $request->get('id'));
        if ($tag) {
            $parentId = $request->get('parentId');
            if ($parentId || $parentId === '0') {
                $tag->setParentId((int)$parentId);
            }
            if ($request->get('text')) {
                $tag->setName(strip_tags($request->get('text', '')));
            }

            $tag->save();

            return $this->adminJson(['success' => true]);
        } else {
            throw $this->createNotFoundException('Tag with ID ' . $request->get('id') . ' not found.');
        }
    }

    #[Route('/tree-get-children-by-id', name: 'pimcore_admin_tags_treegetchildrenbyid', methods: ['GET'])]
    public function treeGetChildrenByIdAction(Request $request): JsonResponse
    {
        $showSelection = $request->get('showSelection') == 'true';
        $assignmentCId = (int)$request->get('assignmentCId');
        $assignmentCType = strip_tags($request->get('assignmentCType', ''));

        $recursiveChildren = false;
        $assignedTagIds = [];
        if ($assignmentCId && $assignmentCType) {
            $assignedTags = Tag::getTagsForElement($assignmentCType, $assignmentCId);

            foreach ($assignedTags as $assignedTag) {
                $assignedTagIds[$assignedTag->getId()] = $assignedTag;
            }
        }

        $tagList = new Tag\Listing();
        if ($request->get('node')) {
            $tagList->setCondition('parentId = ?', (int)$request->get('node'));
        } else {
            $tagList->setCondition('ISNULL(parentId) OR parentId = 0');
        }
        $tagList->setOrderKey('name');

        if (!empty($request->get('filter'))) {
            $filterIds = [0];
            $filterTagList = new Tag\Listing();
            $filterTagList->setCondition('LOWER(`name`) LIKE ?', ['%' . $filterTagList->escapeLike(mb_strtolower($request->get('filter'))) . '%']);
            foreach ($filterTagList->load() as $filterTag) {
                if ($filterTag->getParentId() === 0) {
                    $filterIds[] = $filterTag->getId();
                } else {
                    $ids = explode('/', $filterTag->getIdPath());
                    if (isset($ids[1])) {
                        $filterIds[] = (int)$ids[1];
                    }
                }
            }

            $filterIds = array_unique(array_values($filterIds));
            $tagList->setCondition('id IN('.implode(',', $filterIds).')');
            $recursiveChildren = true;
        }

        $tags = [];
        foreach ($tagList->load() as $tag) {
            $tags[] = $this->convertTagToArray($tag, $showSelection, $assignedTagIds, true, $recursiveChildren);
        }

        return $this->adminJson($tags);
    }

    protected function convertTagToArray(Tag $tag, bool $showSelection, array $assignedTagIds, bool $loadChildren = false, bool $recursiveChildren = false): array
    {
        $hasChildren = $tag->hasChildren();

        $tagArray = [
            'id' => $tag->getId(),
            'text' => $tag->getName(),
            'path' => $tag->getNamePath(),
            'expandable' => $hasChildren,
            'leaf' => !$hasChildren,
            'iconCls' => 'pimcore_icon_element_tags',
            'qtipCfg' => [
                'title' => 'ID: ' . $tag->getId(),
            ],
        ];

        if ($showSelection) {
            $tagArray['checked'] = isset($assignedTagIds[$tag->getId()]);
        }

        if ($hasChildren && $loadChildren) {
            $children = $tag->getChildren();
            $loadChildren = $recursiveChildren;
            foreach ($children as $child) {
                $tagArray['children'][] = $this->convertTagToArray($child, $showSelection, $assignedTagIds, $loadChildren, $recursiveChildren);
            }
        }

        return $tagArray;
    }

    #[Route('/load-tags-for-element', name: 'pimcore_admin_tags_loadtagsforelement', methods: ['GET'])]
    public function loadTagsForElementAction(Request $request): JsonResponse
    {
        $assginmentCId = (int)$request->get('assignmentCId');
        $assginmentCType = strip_tags($request->get('assignmentCType', ''));

        $assignedTagArray = [];
        if ($assginmentCId && $assginmentCType) {
            $assignedTags = Tag::getTagsForElement($assginmentCType, $assginmentCId);

            foreach ($assignedTags as $assignedTag) {
                $assignedTagArray[] = $this->convertTagToArray($assignedTag, false, []);
            }
        }

        return $this->adminJson($assignedTagArray);
    }

    #[Route('/add-tag-to-element', name: 'pimcore_admin_tags_addtagtoelement', methods: ['PUT'])]
    public function addTagToElementAction(Request $request): JsonResponse
    {
        $assginmentCId = (int)$request->get('assignmentElementId');
        $assginmentCType = strip_tags($request->get('assignmentElementType', ''));
        $tagId = (int)$request->get('tagId');

        $tag = Tag::getById($tagId);
        if ($tag) {
            Tag::addTagToElement($assginmentCType, $assginmentCId, $tag);

            return $this->adminJson(['success' => true, 'id' => $tag->getId()]);
        } else {
            return $this->adminJson(['success' => false]);
        }
    }

    #[Route('/remove-tag-from-element', name: 'pimcore_admin_tags_removetagfromelement', methods: ['DELETE'])]
    public function removeTagFromElementAction(Request $request): JsonResponse
    {
        $assginmentCId = (int)$request->get('assignmentElementId');
        $assginmentCType = strip_tags($request->get('assignmentElementType', ''));
        $tagId = (int)$request->get('tagId');

        $tag = Tag::getById($tagId);
        if ($tag) {
            Tag::removeTagFromElement($assginmentCType, $assginmentCId, $tag);

            return $this->adminJson(['success' => true, 'id' => $tag->getId()]);
        } else {
            return $this->adminJson(['success' => false]);
        }
    }

    #[Route('/get-batch-assignment-jobs', name: 'pimcore_admin_tags_getbatchassignmentjobs', methods: ['GET'])]
    public function getBatchAssignmentJobsAction(Request $request, EventDispatcherInterface $eventDispatcher): JsonResponse
    {
        $elementId = (int)$request->get('elementId');
        $elementType = strip_tags($request->get('elementType', ''));

        $idList = [];
        switch ($elementType) {
            case 'object':
                $object = \Pimcore\Model\DataObject::getById($elementId);
                if ($object) {
                    $idList = $this->getSubObjectIds($object, $eventDispatcher);
                }

                break;
            case 'asset':
                $asset = \Pimcore\Model\Asset::getById($elementId);
                if ($asset) {
                    $idList = $this->getSubAssetIds($asset, $eventDispatcher);
                }

                break;
            case 'document':
                $document = \Pimcore\Model\Document::getById($elementId);
                if ($document) {
                    $idList = $this->getSubDocumentIds($document, $eventDispatcher);
                }

                break;
        }

        $size = 2;
        $offset = 0;
        $idListParts = [];
        while ($offset < count($idList)) {
            $idListParts[] = array_slice($idList, $offset, $size);
            $offset += $size;
        }

        return $this->adminJson(['success' => true, 'idLists' => $idListParts, 'totalCount' => count($idList)]);
    }

    /**
     * @return int[]
     */
    private function getSubObjectIds(\Pimcore\Model\DataObject\AbstractObject $object, EventDispatcherInterface $eventDispatcher): array
    {
        $childrenList = new \Pimcore\Model\DataObject\Listing();
        $condition = '`path` LIKE ?';
        if (!$this->getAdminUser()->isAdmin()) {
            $userIds = $this->getAdminUser()->getRoles();
            $userIds[] = $this->getAdminUser()->getId();
            $condition .= ' AND (
                (SELECT `view` FROM users_workspaces_object WHERE userId IN (' . implode(',', $userIds) . ') and LOCATE(CONCAT(`path`,`key`),cpath)=1  ORDER BY LENGTH(cpath) DESC LIMIT 1)=1
                    OR
                (SELECT `view` FROM users_workspaces_object WHERE userId IN (' . implode(',', $userIds) . ') and LOCATE(cpath,CONCAT(`path`,`key`))=1  ORDER BY LENGTH(cpath) DESC LIMIT 1)=1
             )';
        }

        $childrenList->setCondition($condition, $childrenList->escapeLike($object->getRealFullPath()) . '/%');

        $beforeListLoadEvent = new GenericEvent($this, [
            'list' => $childrenList,
            'context' => [],
        ]);
        $eventDispatcher->dispatch($beforeListLoadEvent, AdminEvents::OBJECT_LIST_BEFORE_LIST_LOAD);
        /** @var \Pimcore\Model\DataObject\Listing $childrenList */
        $childrenList = $beforeListLoadEvent->getArgument('list');

        return $childrenList->loadIdList();
    }

    /**
     * @return int[]
     */
    private function getSubAssetIds(\Pimcore\Model\Asset $asset, EventDispatcherInterface $eventDispatcher): array
    {
        $childrenList = new \Pimcore\Model\Asset\Listing();
        $condition = '`path` LIKE ?';
        if (!$this->getAdminUser()->isAdmin()) {
            $userIds = $this->getAdminUser()->getRoles();
            $userIds[] = $this->getAdminUser()->getId();
            $condition .= ' AND (
                (SELECT `view` FROM users_workspaces_asset WHERE userId IN (' . implode(',', $userIds) . ') and LOCATE(CONCAT(`path`,filename),cpath)=1  ORDER BY LENGTH(cpath) DESC LIMIT 1)=1
                    OR
                (SELECT `view` FROM users_workspaces_asset WHERE userId IN (' . implode(',', $userIds) . ') and LOCATE(cpath,CONCAT(`path`,filename))=1  ORDER BY LENGTH(cpath) DESC LIMIT 1)=1
            )';
        }

        $childrenList->setCondition($condition, $childrenList->escapeLike($asset->getRealFullPath()) . '/%');

        $beforeListLoadEvent = new GenericEvent($this, [
            'list' => $childrenList,
            'context' => [],
        ]);
        $eventDispatcher->dispatch($beforeListLoadEvent, AdminEvents::ASSET_LIST_BEFORE_LIST_LOAD);
        /** @var \Pimcore\Model\Asset\Listing $childrenList */
        $childrenList = $beforeListLoadEvent->getArgument('list');

        return $childrenList->loadIdList();
    }

    /**
     * @return int[]
     */
    private function getSubDocumentIds(\Pimcore\Model\Document $document, EventDispatcherInterface $eventDispatcher): array
    {
        $childrenList = new \Pimcore\Model\Document\Listing();
        $condition = '`path` LIKE ?';
        if (!$this->getAdminUser()->isAdmin()) {
            $userIds = $this->getAdminUser()->getRoles();
            $userIds[] = $this->getAdminUser()->getId();
            $condition .= ' AND (
                (SELECT `view` FROM users_workspaces_document WHERE userId IN (' . implode(',', $userIds) . ') and LOCATE(CONCAT(`path`,`key`),cpath)=1  ORDER BY LENGTH(cpath) DESC LIMIT 1)=1
                    OR
                (SELECT `view` FROM users_workspaces_document WHERE userId IN (' . implode(',', $userIds) . ') and LOCATE(cpath,CONCAT(`path`,`key`))=1  ORDER BY LENGTH(cpath) DESC LIMIT 1)=1
            )';
        }

        $childrenList->setCondition($condition, $childrenList->escapeLike($document->getRealFullPath()) . '/%');

        $beforeListLoadEvent = new GenericEvent($this, [
            'list' => $childrenList,
            'context' => [],
        ]);
        $eventDispatcher->dispatch($beforeListLoadEvent, AdminEvents::DOCUMENT_LIST_BEFORE_LIST_LOAD);
        /** @var \Pimcore\Model\Document\Listing $childrenList */
        $childrenList = $beforeListLoadEvent->getArgument('list');

        return $childrenList->loadIdList();
    }

    #[Route('/do-batch-assignment', name: 'pimcore_admin_tags_dobatchassignment', methods: ['PUT'])]
    public function doBatchAssignmentAction(Request $request): JsonResponse
    {
        $cType = strip_tags($request->get('elementType', ''));
        $assignedTags = json_decode($request->get('assignedTags'));
        $elementIds = json_decode($request->get('childrenIds'));
        $doCleanupTags = $request->get('removeAndApply') == 'true';

        Tag::batchAssignTagsToElement($cType, $elementIds, $assignedTags, $doCleanupTags);

        return $this->adminJson(['success' => true]);
    }
}
