<?php

/** @noinspection PhpDocMissingThrowsInspection */

/** @noinspection PhpUnhandledExceptionInspection */

namespace DefStudio\GameEngine;

use Illuminate\Support\Collection;
use DefStudio\GameEngine\Enums\Role;
use DefStudio\GameEngine\Models\Map;
use DefStudio\GameEngine\Models\Run;
use Illuminate\Support\Facades\Auth;
use DefStudio\GameEngine\Models\Task;
use DefStudio\GameEngine\Models\Team;
use DefStudio\GameEngine\Models\User;
use DefStudio\GameEngine\Models\Award;
use DefStudio\GameEngine\Models\Story;
use DefStudio\GameEngine\Enums\Feature;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Session;
use DefStudio\GameEngine\Models\Mission;
use Illuminate\Database\Eloquent\Builder;
use DefStudio\GameEngine\Enums\Permission;
use DefStudio\GameEngine\Models\Organization;
use DefStudio\GameEngine\Models\Storytelling;
use Symfony\Component\HttpFoundation\Response;
use DefStudio\GameEngine\Enums\StorytellingType;
use DefStudio\GameEngine\Exceptions\AwardException;
use DefStudio\GameEngine\Decorators\Tasks\TaskDecorator;
use DefStudio\GameEngine\Models\Contracts\AwardsDefiner;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use DefStudio\GameEngine\Actions\Missions\Extractor\Contracts\TaskExtractor;

class GameEngine
{
    public function navigation_menu_entries(): Collection
    {
        $user = $this->user(true);

        if (!$user instanceof User) {
            return Collection::empty();
        }

        return Collection::make([
            [
                'label' => __('Dashboard'),
                'url' => route('game-engine.dashboard'),
                'active' => request()->route()?->getName() === 'dashboard',
            ],
        ])->when($user->can('viewAny', $this->userClass()), fn(Collection $menu) => $menu->push([
            'label' => __('Users'),
            'url' => route('game-engine.admin.users.index'),
            'active' => str_starts_with(request()->route()?->getName(), 'game-engine.admin.users'),
        ]))->when($user->can('viewAny', $this->organizationClass()), fn(Collection $menu) => $menu->push([
            'label' => __('Organizations'),
            'url' => route('game-engine.admin.organizations.index'),
            'active' => request()->route()?->getName() === 'game-engine.admin.organizations.index',
        ]))->when($user->current_organization_id && $user->can('administrate', $user->current_organization), fn(Collection $menu) => $menu->push([
            'label' => $user->current_organization->name,
            'url' => route('game-engine.admin.organizations.edit', $user->current_organization),
            'active' => request()->route()?->getName() !== 'game-engine.admin.organizations.index' && str_starts_with(request()->route()?->getName(), 'game-engine.admin.organizations'),
        ]))->when($user->current_organization_id && $user->can('viewAny', $this->teamClass()), fn(Collection $menu) => $menu->push([
            'label' => __('Teams'),
            'url' => route('game-engine.admin.teams.index'),
            'active' => str_starts_with(request()->route()?->getName(), 'game-engine.admin.teams'),
        ]))->when($user->current_organization_id && $user->can('viewAny', $this->storyClass()), fn(Collection $menu) => $menu->push([
            'label' => __('Stories'),
            'url' => route('game-engine.admin.stories.index'),
            'active' => str_starts_with(request()->route()?->getName(), 'game-engine.admin.stories'),
        ]))->when($user->hasRole(Role::super_admin) || $user->current_organization_id && $user->can(Permission::manage_awards->value), fn(Collection $menu) => $menu->push([
            'label' => __('Awards'),
            'url' => route('game-engine.admin.awards.index'),
            'active' => str_starts_with(request()->route()?->getName(), 'game-engine.admin.awards'),
        ]));
    }

    public function role(Role $role): bool
    {
        return $this->user()->hasRole($role);
    }

    /**
     * @return ($allow_guests is true ? User|null : User)
     *
     * @noinspection PhpMissingReturnTypeInspection
     */
    public function user(bool $allow_guests = false)
    {
        /** @var User|null $user */
        $user = Auth::user();

        if (!$allow_guests) {
            abort_if(!$user instanceof User, Response::HTTP_UNAUTHORIZED);
        }

        return $user;
    }

    /**
     * @return class-string<User>
     */
    public function userClass(): string
    {
        return config('auth.providers.users.model');
    }

    public function userMorphClass(): string
    {
        return config('game-engine.users.morph_class');
    }

    /**
     * @return Builder<User>
     */
    public function userQuery(): Builder
    {
        return app($this->userClass())->newModelQuery();
    }

    /**
     * @return class-string<Organization>
     */
    public function organizationClass(): string
    {
        return config('game-engine.organizations.model');
    }

    public function organizationMorphClass(): string
    {
        return config('game-engine.organizations.morph_class');
    }

    /**
     * @return Builder<Organization>
     */
    public function organizationQuery(): Builder
    {
        return app($this->organizationClass())->newModelQuery();
    }

    public function visibleOrganizationQuery(): Builder|BelongsToMany
    {
        return $this->user()->hasRole(Role::super_admin)
            ? $this->organizationQuery()
            : $this->user()->organizations();
    }

    /**
     * @return class-string<Team>
     */
    public function teamClass(): string
    {
        return config('game-engine.teams.model');
    }

    public function teamMorphClass(): string
    {
        return config('game-engine.teams.morph_class');
    }

    /**
     * @return Builder<Team>
     */
    public function teamQuery(): Builder
    {
        return app($this->teamClass())->newModelQuery();
    }

    /**
     * @return class-string<Story>
     */
    public function storyClass(): string
    {
        return config('game-engine.stories.model');
    }

    public function storyMorphClass(): string
    {
        return config('game-engine.stories.morph_class');
    }

    /**
     * @return Builder<Story>
     */
    public function storyQuery(): Builder
    {
        return app($this->storyClass())->newModelQuery();
    }

    /**
     * @return class-string<Map>
     */
    public function mapClass(): string
    {
        return config('game-engine.maps.model');
    }

    public function mapMorphClass(): string
    {
        return config('game-engine.maps.morph_class');
    }

    /**
     * @return Builder<Map>
     */
    public function mapQuery(): Builder
    {
        return app($this->mapClass())->newModelQuery();
    }

    /**
     * @return class-string<Mission>
     */
    public function missionClass(): string
    {
        return config('game-engine.missions.model');
    }

    public function missionMorphClass(): string
    {
        return config('game-engine.missions.morph_class');
    }

    /**
     * @return Builder<Mission>
     */
    public function missionQuery(): Builder
    {
        return app($this->missionClass())->newModelQuery();
    }

    /**
     * @return class-string<Task>
     */
    public function taskClass(): string
    {
        return config('game-engine.tasks.model');
    }

    public function taskMorphClass(): string
    {
        return config('game-engine.tasks.morph_class');
    }

    /**
     * @return Builder<Task>
     */
    public function taskQuery(): Builder
    {
        return app($this->taskClass())->newModelQuery();
    }

    /**
     * @return class-string<Storytelling>
     */
    public function storytellingClass(): string
    {
        return config('game-engine.storytelling.model');
    }

    public function storytellingMorphClass(): string
    {
        return config('game-engine.storytelling.morph_class');
    }

    /**
     * @return Builder<Storytelling>
     */
    public function storytellingQuery(): Builder
    {
        return app($this->storytellingClass())->newModelQuery();
    }

    /**
     * @return class-string<Award>
     */
    public function awardClass(): string
    {
        return config('game-engine.awards.model');
    }

    public function awardMorphClass(): string
    {
        return config('game-engine.awards.morph_class');
    }

    /**
     * @return Builder<Award>
     */
    public function awardQuery(): Builder
    {
        return app($this->awardClass())->newModelQuery();
    }

    /**
     * @return class-string<Run>
     */
    public function runClass(): string
    {
        return config('game-engine.runs.model');
    }

    public function runMorphClass(): string
    {
        return config('game-engine.runs.morph_class');
    }

    /**
     * @return Builder<Run>
     */
    public function runQuery(): Builder
    {
        return app($this->runClass())->newModelQuery();
    }

    public function back_url(): string
    {
        return Session::get('back_url') ?? url()->previous();
    }

    public function flash_back_url(string $url): void
    {
        Session::flash('back_url', $url);
    }

    /**
     * @return Collection<int, TaskDecorator>
     */
    public function available_task_types(): Collection
    {
        return collect(config('game-engine.tasks.types'));
    }

    /**
     * @return Collection<int, StorytellingType>
     */
    public function available_storytelling_types(): Collection
    {
        return collect(config('game-engine.storytelling.types'));
    }

    /**
     * @return Collection<int, class-string<Model&AwardsDefiner>>
     */
    public function available_award_scopes(): Collection
    {
        if (Feature::scoped_awards->disabled()) {
            return Collection::empty();
        }

        return collect(config('game-engine.awards.scopes'))
            ->each(fn(string $scope_class) => throw_unless(isset(class_implements($scope_class)[AwardsDefiner::class]), AwardException::invalid_scope($scope_class)));
    }

    public function task_extractor(): TaskExtractor
    {
        return app(config('game-engine.missions.task_extractor'));
    }
}
