<?php
declare(strict_types=1);

namespace Celena\Core\Security;

/**
 * Простая RBAC. Роли заданы в коде, разрешения — список строк ("news.create").
 * Расширяется плагинами через Permission::register('shop.orders.refund', ['admin','manager']).
 */
final class Permission
{
    /** @var array<string, string[]> */
    private static array $rolePermissions = [
        'admin'  => ['*'],
        // editor: контент-редактор. templates.edit намеренно НЕ выдаём — это потенциальный
        // вектор XSS и RCE через редактор шаблонов / preview. Только admin.
        'editor' => ['news.*', 'pages.*', 'categories.*', 'comments.*', 'media.*', 'customfields.read', 'ai.use'],
        'author' => ['news.create', 'news.update', 'news.read', 'media.upload', 'media.read', 'ai.use'],
        'user'   => [],
        // Покупатель фронта (личный кабинет /account). Никаких прав в админке —
        // AuthMiddleware дополнительно редиректит эту роль из /admin на /account.
        'customer' => [],
    ];

    public static function can(?array $user, string $permission): bool
    {
        if ($user === null) {
            return false;
        }
        $role = (string) ($user['role'] ?? 'user');
        $perms = self::$rolePermissions[$role] ?? [];
        foreach ($perms as $p) {
            if ($p === '*' || $p === $permission) {
                return true;
            }
            if (str_ends_with($p, '.*')) {
                $prefix = substr($p, 0, -2);
                if (str_starts_with($permission, $prefix . '.') || $permission === $prefix) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Расширить список разрешений роли в рантайме (используется плагинами).
     *
     * ВНИМАНИЕ: метод мутирует static-state, действует на весь процесс и не имеет
     * проверок инициатора. Любой загруженный код (включая плагин) может выдать
     * себе или другой роли произвольное разрешение, в т.ч. `*` или `users.*`.
     *
     * Это by-design плагинного API: установка/активация плагина уже требует роли
     * admin (см. `plugins.install`), и плагин на этом этапе исполняет произвольный
     * PHP-код. Поэтому grant() — не граница безопасности, а удобный хук.
     *
     * Не вызывайте grant() из обработчиков пользовательского ввода.
     */
    public static function grant(string $role, string|array $permissions): void
    {
        $list = is_array($permissions) ? $permissions : [$permissions];
        foreach ($list as $p) {
            self::$rolePermissions[$role][] = $p;
        }
    }

    /** @return string[] */
    public static function roles(): array
    {
        return array_keys(self::$rolePermissions);
    }
}
