<?php

namespace Ignite\Core\Contracts\Entities\Filters;

use Ignite\Core\Entities\User;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Query\Builder as QueryBuilder;

/**
 * This provides the ability to scope a query (Query builder or Eloquent builder query), typically for a user.
 * The reason for not using Eloquent's global scopes are:
 *   - We wanted the ability to apply these scopes not only to Eloquent queries but also to Laravel's DBAL queries.
 *   - We wanted to be able to alter the scope applied based on a permission (e.g. browse participants).
 */
interface QueryFilter
{
    /**
     * Gives you the model class this scope is for. Yea, this will require you to define an Eloquent model for us to
     * pull table and primary key information.
     *
     * @return string
     */
    public function modelClass(): string;

    /**
     * Does the given or authed user have the ability to access the given entity?
     *
     * @param Model|int $model The entity that should exist in the scope.
     * @param User|null $user If null, we will use the authed user if any.
     * @return bool
     */
    public function canAccess($model, ?User $user = null): bool;

    /**
     * Apply this scope to a query.
     *
     * @param QueryBuilder|EloquentBuilder $query
     * @param string|null $tableAlias How the model's table is aliased in the given query, if different from the model.
     * @param User|null $user If null, we will use the authed user if any.
     * @return QueryBuilder|EloquentBuilder
     */
    public function apply($query, ?string $tableAlias = null, ?User $user = null);

    /**
     * Assert that this scope is for the given class.
     * (made this part of the interface for \Ignite\Core\Entities\Base::scopeWhereHasPermission. I guess it might be
     * useful somewhere else some day, but does feel a bit weird.)
     *
     * @param string|Model $class
     * @throws \UnexpectedValueException When it is not for the same class.
     */
    public function assertForModelClass($class): void;
}
