<?php

namespace App\Traits;

use Illuminate\Support\Arr;
use App\Models\Permission;
use App\Models\Role;
use App\Models\User;

trait HasPermissionsTrait
{
    /*
     * This trait is used by pages, blogs, content elements, livestreams, courses, staff profiles
     * Users and Roles can be granted permission to these objects (acccessables)
     * Objectables are the pages, blogs, content elements, livestreams
     * Accessables are the users, roles
     */

    public function createPermission($action, $accessable)
    {
        $permission = (new Permission())->savePermission($action, $this, $accessable);
        return $this;
    }

    public function removePermission($action, $accessable)
    {
        $permission = (new Permission())->removePermission($action, $this, $accessable);
        return $this;
    }

    public function permissions()
    {
        return $this->morphMany(Permission::class, 'objectable');
    }

    public function getRolesAttribute()
    {
        return $this->permissions->where('accessable_type', Role::class)->map->accessable;
    }

    public function getUsersAttribute()
    {
        return $this->permissions->where('accessable_type', User::class)->map->accessable;
    }

    public function saveRoles($input, $action)
    {
        $this->permissions()
             ->where('accessable_type', Role::class)
             ->where('action', $action)
             ->delete();

        if (Arr::get($input, 'roles')) {
            foreach (Arr::get($input, 'roles') as $role_data) {
                $this->createPermission($action, Role::findOrFail(Arr::get($role_data, 'id')));
            }
        }
        return $this;
    }

    public function getPolicyMethods()
    {
        $class = 'App\Policies\\'.class_basename($this).'Policy';
        return collect(get_class_methods($class))
            ->diff(['before', 'viewAny', 'forceDelete', 'restore', 'denyAsNotFound', 'denyWithStatus'])
            ->sort()
            ->values();
    }

    /*
     * This is for Vue only, to determine accesses for display links etc
     * We create a collection with the keys as the action name so we can
     * access in javascript as an object and use dot notation
     * page.actions.update or page.action.publish etc.
     * You can find all the available actions in the Polcy Class associated
     * with each object ie /app/Policies/PagePolicy.php
     * The getFoobarAttribute is a Laravel magic method for accessing custom
     * attributes on models: https://laravel.com/docs/eloquent-mutators#defining-an-accessor
     */
    public function getActionsAttribute()
    {
        if (!auth()->check()) {
            return collect();
        }

        return cache()->tags([cache_name($this), cache_name(auth()->user())])->rememberForever(cache_name($this).'-actions-user-'.auth()->user()->id, function () {
            $methods = $this->getPolicyMethods()->filter(function ($method) {
                return auth()->user()->can($method, $this);
            })
            ->flip()
            ->map(function ($action) {
                return true;
            });

            return $methods;
        });
    }

    public function getHasPermissionsAttribute()
    {
        return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-has-permissions', function () {
            return (bool) $this->permissions->count();
        });
    }

    public function hasViewPermissions()
    {
        $uses_versioning = collect(class_uses($this))->contains(function ($class_name) {
            return class_basename($class_name) === 'VersioningTrait';
        });

        if ($uses_versioning) {
            $version = $this->version;

            if (!$version) {
                return false;
            }
        }

        return cache()->tags([cache_name($this)])->rememberForever(cache_name($this).'-has-view-permissions', function () {
            return $this->permissions->where('action', 'view')->count() ? true : false;
        });
    }
}
