<?php

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

use PublishPress\Future\Core\HookableInterface;
use PublishPress\Future\Framework\Logger\LoggerInterface;
use PublishPress\Future\Modules\Workflows\Domain\Steps\Actions\Definitions\DoAction;
use PublishPress\Future\Modules\Workflows\Interfaces\StepRunnerInterface;
use PublishPress\Future\Modules\Workflows\Interfaces\StepProcessorInterface;
use PublishPress\Future\Modules\Workflows\Interfaces\ExecutionContextInterface;

class DoActionRunner implements StepRunnerInterface
{
    /**
     * @var StepProcessorInterface
     */
    private $stepProcessor;

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

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

    /**
     * @var HookableInterface
     */
    private $hooks;

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

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

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

    public function setup(array $step, array $contextVariables = []): void
    {
        $this->stepSlug = $this->stepProcessor->getSlugFromStep($step);

        $this->stepProcessor->setup($step, [$this, 'processDoAction']);
    }

    public function processDoAction(array $step)
    {
        $this->logDebugMessage('Start processing do action');

        $this->stepProcessor->executeSafelyWithErrorHandling(
            $step,
            function ($step) {
                $nodeSlug = $this->stepProcessor->getSlugFromStep($step);
                $node = $this->stepProcessor->getNodeFromStep($step);
                $nodeSettings = $this->stepProcessor->getNodeSettings($node);

                $hook = $nodeSettings['hook'] ?? '';
                $argsStructure = $nodeSettings['args'] ?? [];

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

                    return;
                }

                $args = $this->buildArgs($argsStructure);

                $this->hooks->doAction($hook, ...$args);
            }
        );
    }

    private function buildArgs(array $argsStructure): array
    {
        $args = [];

        foreach ($argsStructure as $arg) {
            $args[] = $this->buildArg($arg);
        }

        return $args;
    }

    /**
     * @param array $arg
     * @return mixed
     */
    private function buildArg(array $arg)
    {
        $hasOnlyOneVariable = substr_count($arg['value'], '{{') === 1;
        if ($hasOnlyOneVariable) {
            $value = $this->executionContext->getVariable($arg['expression']['expression']);
        } else {
            // This will always return a string
            $value = $this->executionContext->resolveExpressionsInText($arg['expression']['expression']);
        }

        // Convert to the expected type
        switch ($arg['type']) {
            case 'string':
                return $value;
            case 'integer':
                return (int) $value;
            case 'boolean':
                return (bool) $value;
            case 'object':
                return json_decode($value);
            case 'array':
                return json_decode($value, true);
            case 'post':
                return get_post($value);
            case 'user':
                return get_user_by('ID', $value);
        }

        return $value;
    }

    private function logDebugMessage(string $message, ...$args): void
    {
        $message .= ' | Slug: ' . $this->stepSlug;

        $this->logger->debug($this->stepProcessor->prepareLogMessage($message, ...$args));
    }
}
