<?php

namespace bff\auth;

trait UserPermissions
{
    /**
     * Permissions list
     * @var array
     */
    protected $permissions;

    /**
     * Open modules/scopes list
     * @var array ['module' => ['scope1', 'scope2', ...], ...]
     */
    protected $openModuleScopes = [
        'users' => ['profile'],
    ];

    /**
     * Set permissions list
     * @param array $permissions
     */
    public function setPermissions(array $permissions)
    {
        $this->permissions = $permissions;
    }

    /**
     * Get permissions list
     * @return array
     */
    public function getPermissions()
    {
        return $this->permissions;
    }

    /**
     * Has access to module (and scopes(s)).
     * @param string $module
     * @param string|array|null $scope
     * @param array|null $permissions
     * @return bool
     */
    protected function hasAccessToModuleScope(string $module, $scope = null, ?array $permissions = null)
    {
        # validate module
        if (empty($module)) {
            return false;
        }
        $module = mb_strtolower($module);

        # validate scope
        if ($scope) {
            if (is_array($scope)) {
                foreach ($scope as $k => $v) {
                    $scope[$k] = mb_strtolower($v);
                }
            } else {
                $scope = mb_strtolower($scope);
            }
        }

        # module scope is open
        if ($this->isOpenModuleScope($module, $scope)) {
            return true;
        }

        # load permissions
        if (is_null($permissions)) {
            $permissions = $this->getPermissions();
        }
        # no permissions
        if (empty($permissions)) {
            return false;
        }

        # no access to module
        $allowedScopes = $permissions[$module] ?? [];
        if (empty($allowedScopes)) {
            return false;
        }

        # full access to module
        if ($module && in_array($module, $allowedScopes)) {
            return true;
        }

        # has access to any scope of module ?
        if ($module && is_null($scope)) {
            # todo: needs explanation
            return true;
        }

        # has access to module scope
        if ($scope) {
            if (is_array($scope)) {
                foreach ($scope as $m) {
                    if ($m && in_array($m, $allowedScopes)) {
                        return true;
                    }
                }
                return false;
            }
            return in_array($scope, $allowedScopes);
        }

        return false;
    }

    /**
     * Check if module scope is in open list.
     * @param string $module
     * @param string|array $scope
     * @return bool
     */
    protected function isOpenModuleScope(string $module, $scope)
    {
        if (is_null($scope)) {
            return false;
        }
        if (is_array($scope)) {
            foreach ($scope as $m) {
                if ($this->isOpenModuleScope($module, $m)) {
                    return true;
                }
            }
            return false;
        }
        return (
            isset($this->openModuleScopes[$module]) &&
            is_array($this->openModuleScopes[$module]) &&
            in_array($scope, $this->openModuleScopes[$module])
        );
    }

    /**
     * Set open modules/scopes list.
     * @param array $list
     * @param bool $merge
     * @return void
     */
    public function setOpenModuleScopes(array $list, $merge = false)
    {
        if ($merge) {
            $this->openModuleScopes = array_merge($this->openModuleScopes, $list);
        } else {
            $this->openModuleScopes = $list;
        }
    }
}