<?php

declare(strict_types=1);

namespace apexl\Io\modules\user\Operator;

use apexl\Io\operators\entityDatabaseOperator;
use apexl\Vault\Vault;

/**
 * Operators-pattern access to `permissions__users` (direct per-user
 * enum-permission grants; stored as PHP-`serialize()`d values).
 *
 * Extracted from UserPermissionManagerService. The manager keeps its
 * public API + PermissionException wrapping; this operator owns the SQL.
 */
class UserPermissionOperator extends entityDatabaseOperator
{
    public const string TABLE = 'permissions__users';

    public function __construct(Vault $vault)
    {
        parent::__construct(self::TABLE, 'user');
        $this->vault = $vault;
    }

    /**
     * Raw query rows ({permission: <serialised>}) for a user, in the
     * shape the permissionCollection factory expects (->fetchAll()).
     */
    public function listForUser(int $userId): array
    {
        return $this->vault->select(self::TABLE)
            ->fields('permission')
            ->where('user', $userId)
            ->execute()
            ->fetchAll();
    }

    /** Idempotent grant — re-granting an existing permission is a no-op, not an error. */
    public function insertForUser(int $userId, string $serialisedPermission): void
    {
        // Raw, so the merge-on-conflict is explicit and behaves identically on
        // vault 3 (which upserts every insert implicitly) and vault 4 (which
        // does not — the collision would surface as an ExecutionException, and
        // UserPermissionManagerService::add() would rethrow it as a
        // PermissionException on what should be a no-op). `user` = `user` is a
        // deliberate no-op: (user, permission) is the whole row. Not vault's
        // upsert(): that method does not exist on 3.1.x, which consumers pin.
        $this->vault->executeSql(
            sprintf(
                'INSERT INTO %s (`user`, `permission`) VALUES (:user, :permission)
                 ON DUPLICATE KEY UPDATE `user` = `user`',
                self::TABLE
            ),
            ['user' => $userId, 'permission' => $serialisedPermission]
        );
    }

    public function deleteForUser(int $userId, string $serialisedPermission): void
    {
        $this->vault->delete(self::TABLE)
            ->where('user', $userId)
            ->where('permission', $serialisedPermission)
            ->execute();
    }

    public function clearForUser(int $userId): void
    {
        $this->vault->delete(self::TABLE)
            ->where('user', $userId)
            ->execute();
    }
}
