<?php

namespace PublishPress\FuturePro\Modules\Workflows\Domain\Steps\Triggers\Runners;

use PublishPress\Future\Core\HookableInterface;
use PublishPress\Future\Framework\Logger\LoggerInterface;
use PublishPress\Future\Modules\Workflows\Domain\Engine\VariableResolvers\ArrayResolver;
use PublishPress\Future\Modules\Workflows\Domain\Engine\VariableResolvers\BooleanResolver;
use PublishPress\Future\Modules\Workflows\Domain\Engine\VariableResolvers\IntegerResolver;
use PublishPress\Future\Modules\Workflows\Domain\Engine\VariableResolvers\PostResolver;
use PublishPress\Future\Modules\Workflows\Domain\Engine\VariableResolvers\StringResolver;
use PublishPress\Future\Modules\Workflows\Domain\Engine\VariableResolvers\UserResolver;
use PublishPress\Future\Modules\Workflows\Domain\Steps\Triggers\Definitions\OnCustomAction;
use PublishPress\Future\Modules\Workflows\Interfaces\ExecutionContextInterface;
use PublishPress\Future\Modules\Workflows\Interfaces\StepProcessorInterface;
use PublishPress\Future\Modules\Workflows\Interfaces\TriggerRunnerInterface;
use PublishPress\FuturePro\Modules\Workflows\HooksAbstract;

class OnCustomActionRunner implements TriggerRunnerInterface
{
    /**
     * @var HookableInterface
     */
    private $hooks;

    /**
     * @var array
     */
    private $step;

    /**
     * @var StepProcessorInterface
     */
    private $stepProcessor;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var ExecutionContextInterface
     */
    private $executionContext;

    /**
     * @var int
     */
    private $workflowId;

    /**
     * @var string
     */
    private $hook;

    /**
     * @var int
     */
    private $priority;

    /**
     * @var array
     */
    private $acceptedArgs;

    /**
     * @var string
     */
    private $nodeSlug;

    public function __construct(
        HookableInterface $hooks,
        StepProcessorInterface $stepProcessor,
        LoggerInterface $logger,
        ExecutionContextInterface $executionContext
    ) {
        $this->hooks = $hooks;
        $this->stepProcessor = $stepProcessor;
        $this->logger = $logger;
        $this->executionContext = $executionContext;
    }

    public static function getNodeTypeName(): string
    {
        return OnCustomAction::getNodeTypeName();
    }

    public function setup(int $workflowId, array $step): void
    {
        $this->step = $step;
        $this->workflowId = $workflowId;
        $this->nodeSlug = $this->step['node']['data']['slug'];

        $nodeSettings = $this->step['node']['data']['settings'] ?? [];

        if (isset($nodeSettings['hook'])) {
            $this->hook = $nodeSettings['hook'];
            $this->priority = isset($nodeSettings['priority']) ? (int) $nodeSettings['priority'] : 10;
            $this->acceptedArgs = isset($nodeSettings['args']) ? (array) $nodeSettings['args'] : [];
        }

        if (empty($this->hook)) {
            $this->logger->error(
                $this->stepProcessor->prepareLogMessage(
                    'No hook found for On Custom Action step %s',
                    $this->nodeSlug
                )
            );

            return;
        }

        $this->hooks->addAction(
            $this->hook,
            [$this, 'triggerCallback'],
            $this->priority,
            count($this->acceptedArgs)
        );
    }

    public function triggerCallback(...$args)
    {
        $acceptedArgsCount = count($this->acceptedArgs);

        // Pass the args to the execution context
        if ($acceptedArgsCount > 0) {
            $args = array_slice($args, 0, $acceptedArgsCount);

            $variables = array_combine(
                array_map(
                    function ($index) {
                        return $this->acceptedArgs[$index]['name'] ?? $index;
                    },
                    array_keys($args)
                ),
                $args
            );

            $i = 0;
            foreach ($variables as $name => &$value) {
                $argumentSettings = $this->acceptedArgs[$i];

                switch ($argumentSettings['type']) {
                    case 'object':
                        $value = (object) $value;
                        break;
                    case 'array':
                        $value = new ArrayResolver((array) $value);
                        break;
                    case 'string':
                        $value = new StringResolver((string) $value);
                        break;
                    case 'integer':
                        $value = new IntegerResolver((int) $value);
                        break;
                    case 'boolean':
                        $value = new BooleanResolver((bool) $value);
                        break;
                    case 'post':
                        $value = new PostResolver($value, $this->hooks);
                        break;
                    case 'user':
                        $value = new UserResolver($value, $this->hooks);
                        break;
                }

                $i++;
            }

            $this->executionContext->setVariable($this->nodeSlug, $variables);
        }

        $this->stepProcessor->executeSafelyWithErrorHandling(
            $this->step,
            function ($step) {
                $this->stepProcessor->triggerCallbackIsRunning();

                $this->hooks->doAction(
                    HooksAbstract::ACTION_WORKFLOW_TRIGGER_EXECUTED,
                    $this->workflowId,
                    $this->step
                );

                $this->stepProcessor->runNextSteps($this->step);
            },
            $this->step
        );
    }
}
