<?php

/** @noinspection PhpUnhandledExceptionInspection */

namespace DefStudio\GameEngine\Http\Livewire\Runs\Concerns;

use DefStudio\GameEngine\Enums\StorytellingType;
use DefStudio\GameEngine\Decorators\ModelDecorator;
use DefStudio\GameEngine\Decorators\Tasks\TaskDecorator;
use DefStudio\GameEngine\Exceptions\StorytellingException;
use DefStudio\GameEngine\Models\Contracts\WithStorytelling;

trait RunsStorytellings
{
    /** @var array */
    public $current_storytellings = [];

    protected function setup_storytellings(ModelDecorator|WithStorytelling $model, bool $force = false): void
    {
        if ($model instanceof ModelDecorator) {
            $model = $model->model;
        }

        if ($model instanceof TaskDecorator) {
            $model = $model->model;
        }

        if (!$model instanceof WithStorytelling) {
            return;
        }

        $scope = $this->get_storytelling_scope($model);

        if (!$force) {
            $this->reset_storytellings($scope);
        }

        if ($this->run->get_state("$scope.storytelling") !== null) {
            return;
        }

        foreach (StorytellingType::cases() as $storytelling_type) {
            $this->run->set_state("$scope.storytelling.$storytelling_type->value", $model->storytellings->where('type', $storytelling_type)->isEmpty());
        }

        $this->run->save();
    }

    protected function reset_storytellings(string|ModelDecorator|WithStorytelling $scope): void
    {
        $scope = $this->get_storytelling_scope($scope);

        $this->run->set_state("$scope.storytelling", null);
    }

    protected function get_storytelling_scope(string|ModelDecorator|WithStorytelling $scope): string
    {
        if (is_string($scope)) {
            return $scope;
        }

        if ($scope instanceof ModelDecorator) {
            $scope = $scope->model;
        }

        if ($scope instanceof TaskDecorator) {
            $scope = $scope->model;
        }

        return str($scope::class)->classBasename()->lower()->toString();
    }

    public function complete_storytelling(string|ModelDecorator|WithStorytelling $scope, StorytellingType|string $type, bool $inline = false): void
    {
        $scope = $this->get_storytelling_scope($scope);

        if (is_string($type)) {
            $type = StorytellingType::from($type);
        }

        $this->run->set_state("$scope.storytelling.$type->value", true);
        $this->run->save();

        $this->call_traits_method('complete_storytelling', $scope, $type);

        if ($this->has_inline_storytellings() && $inline) {
            $this->current_storytellings = [];
        } elseif (!$this->has_inline_storytellings() && !$inline) {

            if ($this->storytellings_waiting_to_open()->isEmpty()) {
                $this->emit('closeModal', true);
            }
        }
    }

    protected function storytellings_waiting_to_open(): \Illuminate\Support\Collection
    {
        return collect($this->getEventQueue())
            ->filter(fn(array $event_data) => $event_data['event'] === 'openModal')
            ->filter(fn(array $event_data) => ($event_data['params'][0] ?? null) === 'game-engine.runner.storytelling');
    }

    protected function has_pending_storytelling(string|ModelDecorator|WithStorytelling $scope, StorytellingType|string $type = null): bool
    {
        $scope = $this->get_storytelling_scope($scope);

        if ($type === null) {
            return collect($this->run->get_state("$scope.storytelling", []))->reject(fn(bool $complete) => $complete)->isNotEmpty();
        }

        if (is_string($type)) {
            $type = StorytellingType::from($type);
        }

        $done = $this->run->get_state("$scope.storytelling.$type->value");

        throw_if($done === null, StorytellingException::not_set_up($scope));

        return !$done;
    }

    protected function has_inline_storytellings(): bool
    {
        if (!property_exists($this, 'inline_storytellings')) {
            return false;
        }

        return $this->inline_storytellings;
    }

    protected function show_storytelling(ModelDecorator|WithStorytelling|string $scope, StorytellingType|string ...$type): bool
    {
        if (is_string($scope)) {
            $scope = $this->{$scope};
        }

        $type = collect($type)
            ->map(fn(string|StorytellingType $t) => $t instanceof StorytellingType ? $t : StorytellingType::from($t))
            ->first(fn(StorytellingType $t) => $this->has_pending_storytelling($scope, $t));

        if ($type === null) {
            return false;
        }

        $storytellings_data = [
            'scope' => $this->get_storytelling_scope($scope),
            'type' => $type->name,
            'storytelling_ids' => $scope->storytellings->where('type', $type)->pluck('id')->toArray(),
        ];

        if ($this->has_inline_storytellings()) {
            $this->current_storytellings = $storytellings_data;
        } else {
            $this->emit('openModal', 'game-engine.runner.storytelling', [...$storytellings_data, 'isModal' => true]);
        }

        return true;
    }
}
