<?php
declare(strict_types=1);
namespace Workshop;

use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Workflow\{Definition, StateMachine, Transition, TransitionBlocker, Workflow};
use Symfony\Component\Workflow\Event\GuardEvent;
use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore;

final class Processes
{
    public array $events = [];
    public function ticket(): StateMachine
    {
        $definition = new Definition(['draft', 'submitted', 'approved', 'rejected'], [
            new Transition('submit', 'draft', 'submitted'),
            new Transition('approve', 'submitted', 'approved'),
            new Transition('reject', 'submitted', 'rejected'),
            new Transition('reopen', 'rejected', 'draft'),
        ], 'draft');
        $dispatcher = new EventDispatcher();
        $dispatcher->addListener('workflow.ticket.guard', function (GuardEvent $event): void {
            /** @var Ticket $ticket */
            $ticket = $event->getSubject();
            $transition = $event->getTransition()->getName();
            if (in_array($transition, ['approve', 'reject'], true)
                && !in_array($ticket->actorRole, ['manager', 'director'], true)) {
                $event->addTransitionBlocker(new TransitionBlocker('Нужна роль manager или director.', 'role_required'));
            }
            if ($transition === 'approve' && $ticket->amount > 100000 && $ticket->actorRole !== 'director') {
                $event->addTransitionBlocker(new TransitionBlocker('Сумму больше 100 000 ₽ подтверждает director.', 'amount_limit'));
            }
            if ($transition === 'reject' && trim($ticket->reason) === '') {
                $event->addTransitionBlocker(new TransitionBlocker('Укажите причину возврата.', 'reason_required'));
            }
        });
        foreach (['guard', 'leave', 'transition', 'enter', 'entered', 'completed', 'announce'] as $name) {
            $dispatcher->addListener('workflow.'.$name, function ($event) use ($name): void {
                $this->events[] = ['event' => $name, 'transition' => $event->getTransition()?->getName(), 'marking' => $event->getMarking()->getPlaces()];
            });
        }
        return new StateMachine($definition, new MethodMarkingStore(true, 'marking'), $dispatcher, 'ticket');
    }
    public function parallel(): Workflow
    {
        return new Workflow(new Definition(
            ['draft', 'legal_pending', 'tech_pending', 'legal_done', 'tech_done', 'ready'],
            [
                new Transition('start', 'draft', ['legal_pending', 'tech_pending']),
                new Transition('legal_ok', 'legal_pending', 'legal_done'),
                new Transition('tech_ok', 'tech_pending', 'tech_done'),
                new Transition('finish', ['legal_done', 'tech_done'], 'ready'),
            ], 'draft'), new MethodMarkingStore(false, 'marking'), null, 'parallel_review');
    }
    public static function inspect(Workflow $workflow, object $subject): array
    {
        $allowed = []; $blocked = [];
        foreach ($workflow->getDefinition()->getTransitions() as $transition) {
            $name = $transition->getName();
            $blockers = $workflow->buildTransitionBlockerList($subject, $name);
            if ($blockers->isEmpty()) { $allowed[] = $name; continue; }
            $blocked[$name] = [];
            foreach ($blockers as $blocker) {
                $blocked[$name][] = ['code' => $blocker->getCode(), 'message' => $blocker->getMessage()];
            }
        }
        return ['marking' => $workflow->getMarking($subject)->getPlaces(), 'allowed' => $allowed, 'blocked' => $blocked];
    }
}
