<?php


namespace SergiX44\Nutgram\Conversations;

use Psr\SimpleCache\InvalidArgumentException;
use RuntimeException;
use SergiX44\Nutgram\Nutgram;

/**
 * Class Conversation
 * @package SergiX44\Nutgram\Conversation
 */
abstract class Conversation
{
    protected bool $skipHandlers = false;
    protected bool $skipMiddlewares = false;
    protected ?string $step = 'start';
    protected Nutgram $bot;
    private static bool $refreshInstance = false;
    private ?int $userId = null;
    private ?int $chatId = null;

    public static function begin(Nutgram $bot, ?int $userId = null, ?int $chatId = null, array $data = []): self
    {
        if ($userId xor $chatId) {
            throw new \InvalidArgumentException('You need to provide both userId and chatId.');
        }

        $instance = $bot->getContainer()->get(static::class);
        $instance->userId = $userId;
        $instance->chatId = $chatId;
        $instance($bot, ...$data);

        return $instance;
    }

    /**
     * @return bool
     */
    public function shouldRefreshInstance(): bool
    {
        return self::$refreshInstance;
    }

    /**
     * @param  bool  $flag
     * @return void
     */
    public static function refreshOnDeserialize(bool $flag = true): void
    {
        self::$refreshInstance = $flag;
    }

    /**
     * @param  string  $step
     * @return void
     * @throws InvalidArgumentException
     */
    protected function next(string $step): void
    {
        $this->step = $step;

        $this->bot->stepConversation($this, $this->userId, $this->chatId);
    }

    /**
     * @return void
     * @throws InvalidArgumentException
     */
    protected function end(): void
    {
        $this->bot->endConversation($this->userId, $this->chatId);
        $this->closing($this->bot);
    }

    /**
     * Developer defined function called before the current step.
     * @param  Nutgram  $bot
     * @return void
     */
    protected function beforeStep(Nutgram $bot)
    {
    }

    /**
     * Developer defined function called when the conversation is shut down.
     * @param  Nutgram  $bot
     * @return void
     */
    protected function closing(Nutgram $bot)
    {
    }

    /**
     * Invokes the correct conversation step.
     * @param  Nutgram  $bot
     * @param  mixed  ...$parameters
     * @return mixed
     */
    public function __invoke(Nutgram $bot, ...$parameters): mixed
    {
        if (method_exists($this, $this->step)) {
            $this->bot = $bot;
            $this->beforeStep($bot);
            return $this->{$this->step}($bot, ...$parameters);
        }

        throw new RuntimeException("Conversation step '$this->step' not found.");
    }

    /**
     * @param  Nutgram  $bot
     * @param  int|null  $userId
     * @param  int|null  $chatId
     * @throws InvalidArgumentException
     */
    public function terminate(Nutgram $bot, ?int $userId = null, ?int $chatId = null): void
    {
        $this->bot = $bot;
        $this->bot->endConversation($userId, $chatId);
        $this->closing($this->bot);
    }

    /**
     * @param  bool  $skipHandlers
     * @return Conversation
     */
    protected function setSkipHandlers(bool $skipHandlers): self
    {
        $this->skipHandlers = $skipHandlers;

        return $this;
    }

    /**
     * @param  bool  $skipMiddlewares
     * @return Conversation
     */
    protected function setSkipMiddlewares(bool $skipMiddlewares): self
    {
        $this->skipMiddlewares = $skipMiddlewares;

        return $this;
    }

    /**
     * @return bool
     */
    public function skipHandlers(): bool
    {
        return $this->skipHandlers;
    }

    /**
     * @return bool
     */
    public function skipMiddlewares(): bool
    {
        return $this->skipMiddlewares;
    }

    /**
     * @return array
     */
    public function __serialize(): array
    {
        $attributes = [
            ...get_object_vars($this),
            ...$this->getSerializableAttributes(),
        ];
        unset($attributes['bot']);

        return $attributes;
    }

    /**
     * @return array
     */
    protected function getSerializableAttributes(): array
    {
        return [];
    }

    public function getChatId(): ?int
    {
        return $this->chatId;
    }

    public function getUserId(): ?int
    {
        return $this->userId;
    }
}
