<?php

/*
 * This file is part of the ActiveCollab project.
 *
 * (c) A51 doo <info@activecollab.com>. All rights reserved.
 */

declare(strict_types=1);

namespace ActiveCollab\Module\System\Utils\Conversations;

use ActiveCollab\Module\System\Events\DataObjectLifeCycleEvents\ConversationEvents\ConversationCreatedEvent;
use ActiveCollab\Module\System\Model\Conversation\ConversationInterface;
use ActiveCollab\Module\System\Model\Conversation\CustomConversationInterface;
use ActiveCollab\Module\System\Model\Conversation\GroupConversation;
use ActiveCollab\Module\System\Model\Conversation\OneToOneConversation;
use ActiveCollab\Module\System\Model\Conversation\OwnConversation;
use ActiveCollab\Module\System\Model\Conversation\ParentObjectConversation;
use Angie\Inflector;
use Conversations;
use ConversationUsers;
use DataObject;
use DataObjectPool;
use IMembers;
use LogicException;
use RuntimeException;
use User;

class ConversationFactory implements ConversationFactoryInterface
{
    private ConversationResolverInterface $conversation_resolver;

    public function __construct(ConversationResolverInterface $conversation_resolver)
    {
        $this->conversation_resolver = $conversation_resolver;
    }

    public function create(User $user, string $type, $value): ConversationInterface
    {
        if ($type === ConversationInterface::CONVERSATION_TYPE_CUSTOM) {
            $conversation = $this->createCustomConversation($user, (array) $value);
        } else {
            $conversation = $this->createSmartConversation($user, $type, $value);
        }

        DataObjectPool::announce(new ConversationCreatedEvent($conversation));

        return $conversation;
    }

    private function createCustomConversation(User $user, array $user_ids): CustomConversationInterface
    {
        if ($conversation = $this->conversation_resolver->getCustomConversation([$user->getId(), ...$user_ids])) {
            return $conversation;
        } else {
            if ($this->shouldCreateOwnConversation($user, $user_ids)) {
                $conversation = Conversations::create(['type' => OwnConversation::class]);
            } elseif ($this->shouldCreateOneToOneConversation($user, $user_ids)) {
                $conversation = Conversations::create(['type' => OneToOneConversation::class]);
            } else {
                $conversation = Conversations::create(['type' => GroupConversation::class]);
            }

            $user_ids = array_unique(
                array_merge(
                    [$user->getId()],
                    $user_ids
                )
            );

            $conversation_users_data = [];
            foreach ($user_ids as $user_id) {
                $conversation_users_data[] = [
                    'conversation_id' => $conversation->getId(),
                    'user_id' => $user_id,
                ];
            }

            ConversationUsers::createMany($conversation_users_data);

            return $conversation;
        }
    }

    private function createSmartConversation(User $user, string $type, $value): ConversationInterface
    {
        $parent_type = ucfirst(Inflector::camelize(str_replace('-', '_', $type)));

        if (!class_exists($parent_type) || !is_subclass_of($parent_type, DataObject::class)) {
            throw new RuntimeException("Type '{$type}' is not valid for creating a conversation.");
        }

        $object = DataObjectPool::get($parent_type, (int) $value);

        if (!$object || !($object instanceof IMembers)) {
            throw new RuntimeException("Object for type '{$type}' is not valid for creating a conversation.");
        }

        if (!$object->isMember($user)) {
            throw new LogicException("User #{$user->getId()} is not member of this object.");
        }

        if ($conversation = $this->conversation_resolver->getConversation($user, $object)) {
            return $conversation;
        } else {
            return Conversations::create(
                [
                    'type' => ParentObjectConversation::class,
                    'name' => $object->getName(),
                    'parent_type' => get_class($object),
                    'parent_id' => $object->getId(),
                ]
            );
        }
    }

    private function shouldCreateOwnConversation(User $user, array $user_ids): bool
    {
        return count($user_ids) === 1 && in_array($user->getId(), $user_ids);
    }

    private function shouldCreateOneToOneConversation(User $user, array $user_ids): bool
    {
        return count($user_ids) === 1 && !in_array($user->getId(), $user_ids);
    }
}
