<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\MainBundle\Workflow;

use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSend;
use Chill\MainBundle\Workflow\Exception\HandlerNotFoundException;
use Chill\MainBundle\Workflow\Exception\HandlerWithPublicViewNotFoundException;
use Chill\MainBundle\Workflow\Templating\EntityWorkflowViewMetadataDTO;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Symfony\Component\Workflow\Registry;

/**
 * Manage the handler and performs some operation on handlers.
 *
 * Each handler must implement @{EntityWorkflowHandlerInterface::class}.
 */
class EntityWorkflowManager
{
    /**
     * @param EntityWorkflowHandlerInterface[] $handlers
     */
    public function __construct(private readonly iterable $handlers, private readonly Registry $registry) {}

    public function getHandler(EntityWorkflow $entityWorkflow, array $options = []): EntityWorkflowHandlerInterface
    {
        foreach ($this->handlers as $handler) {
            if ($handler->supports($entityWorkflow, $options)) {
                return $handler;
            }
        }

        throw new HandlerNotFoundException();
    }

    public function getSupportedWorkflows(EntityWorkflow $entityWorkflow): array
    {
        return $this->registry->all($entityWorkflow);
    }

    public function getAssociatedStoredObject(EntityWorkflow $entityWorkflow): ?StoredObject
    {
        foreach ($this->handlers as $handler) {
            if ($handler instanceof EntityWorkflowWithStoredObjectHandlerInterface && $handler->supports($entityWorkflow)) {
                return $handler->getAssociatedStoredObject($entityWorkflow);
            }
        }

        return null;
    }

    /**
     * Return true if the entityWorkflow may associate a stored object.
     *
     * Take care that, for various reasons, the method @see{getAssociatedStoredObject} may return null if, for
     * various reasons, the associated stored object may not be retrieved.
     *
     * @return bool return true if the entityWorkflow may associate a stored object
     */
    public function canAssociateStoredObject(EntityWorkflow $entityWorkflow): bool
    {
        foreach ($this->handlers as $handler) {
            if ($handler instanceof EntityWorkflowWithStoredObjectHandlerInterface && $handler->supports($entityWorkflow)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return list<EntityWorkflow>
     */
    public function findByRelatedEntity(object $object): array
    {
        foreach ($this->handlers as $handler) {
            if ([] !== $workflows = $handler->findByRelatedEntity($object)) {
                return $workflows;
            }
        }

        return [];
    }

    /**
     * Renders the public view for the given entity workflow send.
     *
     * @param EntityWorkflowSend $entityWorkflowSend the entity workflow send object
     *
     * @return string the rendered public view
     *
     * @throws HandlerWithPublicViewNotFoundException if no handler with public view is found
     */
    public function renderPublicView(EntityWorkflowSend $entityWorkflowSend, EntityWorkflowViewMetadataDTO $metadata): string
    {
        $entityWorkflow = $entityWorkflowSend->getEntityWorkflowStep()->getEntityWorkflow();

        foreach ($this->handlers as $handler) {
            if ($handler instanceof EntityWorkflowWithPublicViewInterface && $handler->supports($entityWorkflow)) {
                return $handler->renderPublicView($entityWorkflowSend, $metadata);
            }
        }

        throw new HandlerWithPublicViewNotFoundException();
    }

    /**
     * @return list<User>
     */
    public function getSuggestedUsers(EntityWorkflow $entityWorkflow, bool $addUsersInvolved = true): array
    {
        $users = [];
        if ($addUsersInvolved) {
            foreach ($entityWorkflow->getUsersInvolved() as $user) {
                $users[] = $user;
            }
        }

        foreach ($this->getHandler($entityWorkflow)->getSuggestedUsers($entityWorkflow) as $user) {
            $users[] = $user;
        }

        return array_values(
            // filter objects to remove duplicates
            array_filter(
                $users,
                fn ($o, $k) => array_search($o, $users, true) === $k,
                ARRAY_FILTER_USE_BOTH
            )
        );
    }

    /**
     * @return list<Person>
     */
    public function getSuggestedPersons(EntityWorkflow $entityWorkflow): array
    {
        return $this->getHandler($entityWorkflow)->getSuggestedPersons($entityWorkflow);
    }

    /**
     * @return list<ThirdParty>
     */
    public function getSuggestedThirdParties(EntityWorkflow $entityWorkflow): array
    {
        return $this->getHandler($entityWorkflow)->getSuggestedThirdParties($entityWorkflow);
    }

    public function getRelatedAccompanyingPeriod(EntityWorkflow $entityWorkflow): ?AccompanyingPeriod
    {
        return $this->getHandler($entityWorkflow)->getRelatedAccompanyingPeriod($entityWorkflow);
    }
}
