<?php
/***********************************************************************************
 * The contents of this file are subject to the Extension License Agreement
 * ("Agreement") which can be viewed at
 * https://www.espocrm.com/extension-license-agreement/.
 * By copying, installing downloading, or using this file, You have unconditionally
 * agreed to the terms and conditions of the Agreement, and You may not use this
 * file except in compliance with the Agreement. Under the terms of the Agreement,
 * You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
 * redistribute, market, publish, commercialize, or otherwise transfer rights or
 * usage to the software or any modified version or derivative work of the software
 * created by or for you.
 *
 * Copyright (C) 2024-2025 Letrium Ltd.
 *
 * License ID: f27e70ce6801a13265271f5669c8bc5c
 ************************************************************************************/

namespace Espo\Modules\Project\Classes\Record\Hooks\ProjectTask;

use Espo\Core\Exceptions\Error\Body;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Modules\Project\Entities\ProjectColumn;
use Espo\Modules\Project\Entities\ProjectTask;
use Espo\Modules\Project\Tools\ProjectTask\RecordService;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;

/**
 * @implements SaveHook<ProjectTask>
 */
class BeforeSave implements SaveHook
{
    public function __construct(
        private RecordService $recordService,
        private EntityManager $entityManager,
    ) {}

    public function process(Entity $entity): void
    {
        $this->processMilestone($entity);
        $this->processDependencyCheck($entity);
        $this->processSubTaskCheck($entity);
    }

    /**
     * @throws Forbidden
     */
    private function processDependencyCheck(ProjectTask $entity): void
    {
        if (!$entity->isAttributeChanged('columnId')) {
            return;
        }

        $columnId = $entity->getColumnId();

        if (!$columnId) {
            return;
        }

        $column = $this->entityManager->getRDBRepositoryByClass(ProjectColumn::class)->getById($columnId);

        if (!$column) {
            return;
        }

        if (
            $entity->getFetchedStatus() !== ProjectTask::STATUS_COMPLETED &&
            $column->getMappedStatus() === ProjectTask::STATUS_COMPLETED
        ) {
            $this->recordService->checkCompleteDependency($entity);
            $this->recordService->checkCompleteSubTasks($entity);
        }

        if (
            in_array($entity->getFetchedStatus(), [
                ProjectTask::STATUS_NOT_STARTED,
                ProjectTask::STATUS_DEFERRED,
            ]) &&
            in_array($column->getMappedStatus(), [
                ProjectTask::STATUS_STARTED,
                ProjectTask::STATUS_COMPLETED
            ])
        ) {
            $this->recordService->checkStartDependency($entity);
        }
    }

    /**
     * @throws Forbidden
     */
    private function processSubTaskCheck(ProjectTask $entity): void
    {
        if (!$entity->getParentTaskId()) {
            return;
        }

        if ($entity->isNew()) {
            $parentTask = $this->entityManager
                ->getRDBRepositoryByClass(ProjectTask::class)
                ->getById($entity->getParentTaskId());

            if (!$parentTask) {
                return;
            }

            $entity->setGroupId($parentTask->getGroupId());

            if ($parentTask->getStatus() === ProjectTask::STATUS_COMPLETED) {
                throw Forbidden::createWithBody(
                    'cannotCreateSubTaskInCompletedTask',
                    Body::create()
                        ->withMessageTranslation('cannotCreateSubTaskInCompletedTask', ProjectTask::ENTITY_TYPE),
                );
            }

            if ($parentTask->getStatus() === ProjectTask::STATUS_CANCELED) {
                throw Forbidden::createWithBody(
                    'cannotCreateSubTaskInCanceledTask',
                    Body::create()
                        ->withMessageTranslation('cannotCreateSubTaskInCanceledTask', ProjectTask::ENTITY_TYPE),
                );
            }

            return;
        }

        if (!$entity->isAttributeChanged('groupId')) {
            return;
        }

        throw Forbidden::createWithBody(
            'cannotChangeSubTaskGroup',
            Body::create()->withMessageTranslation('cannotChangeSubTaskGroup', ProjectTask::ENTITY_TYPE),
        );
    }

    /**
     * @throws Forbidden
     */
    private function processMilestone(ProjectTask $entity): void
    {
        if (!$entity->isMilestone()) {
            return;
        }

        if ($entity->getParentTaskId()) {
            throw new Forbidden("Milestone cannot be a sub-task.");
        }

        $entity->setDateEnd(null);

        $preserveAttributes = [
            'hours',
            'points',
            'priority',
        ];

        if (!$entity->isNew()) {
            foreach ($preserveAttributes as $attribute) {
                if ($entity->isAttributeChanged($attribute)) {
                    $entity->set($attribute, $entity->getFetched($attribute));
                }
            }

            return;
        }

        $entity->setPriority(ProjectTask::PRIORITY_NORMAL);
        $entity->setHours(null);
        $entity->setPoints(null);
    }
}
