<?php
namespace App\Values\SmartPlaylist;
use App\Builders\SongBuilder;
use App\Enums\SmartPlaylistModel as Model;
use App\Enums\SmartPlaylistOperator as Operator;
use App\Values\SmartPlaylist\SmartPlaylistRule as Rule;
use Carbon\Carbon;
use Closure;
use Illuminate\Database\Eloquent\Builder;
final class SmartPlaylistQueryModifier
{
private static function resolveWhereMethod(Rule $rule, Operator $operator): string
{
return $rule->model->requiresRawQuery()
? 'whereRaw'
: $operator->toWhereMethod();
}
public static function applyRule(Rule $rule, SongBuilder $query): void
{
$operator = $rule->operator;
$value = $rule->value;
if ($rule->model->isDate() && in_array($operator, [Operator::IS, Operator::IS_NOT], true)) {
$operator = $operator === Operator::IS ? Operator::IS_BETWEEN : Operator::IS_NOT_BETWEEN;
$nextDay = Carbon::createFromFormat('Y-m-d', $value[0])->addDay()->format('Y-m-d');
$value = [$value[0], $nextDay];
}
if ($rule->model->getManyToManyRelation()) {
$whereHasClause = $rule->operator->isNegative() ? 'whereDoesntHave' : 'whereHas';
$operator = match ($operator) {
Operator::IS_NOT => Operator::IS,
Operator::NOT_CONTAIN => Operator::CONTAINS,
Operator::IS_NOT_BETWEEN => Operator::IS_BETWEEN,
default => $operator,
};
$query->{$whereHasClause}(
$rule->model->getManyToManyRelation(),
static function (Builder $subQuery) use ($rule, $operator, $value): void {
$subQuery->{self::resolveWhereMethod($rule, $operator)}(
...self::generateParameters($rule->model, $operator, $value)
);
}
);
} else {
$query->{self::resolveWhereMethod($rule, $operator)}(
...self::generateParameters($rule->model, $operator, $value)
);
}
}
private static function generateParameters(Model $model, Operator $operator, array $value): array
{
$column = $model->toColumnName();
$parameters = $model->requiresRawQuery()
? self::generateRawParameters($column, $operator, $value)
: self::generateEloquentParameters($column, $operator, $value);
return $parameters instanceof Closure ? $parameters() : $parameters;
}
private static function generateRawParameters(string $column, Operator $operator, array $value): array|Closure
{
return match ($operator) {
Operator::BEGINS_WITH => ["$column LIKE ?", ["{$value[0]}%"]],
Operator::CONTAINS => ["$column LIKE ?", ["%{$value[0]}%"]],
Operator::ENDS_WITH => ["$column LIKE ?", ["%{$value[0]}"]],
Operator::IN_LAST => static fn () => ["$column >= ?", [now()->subDays($value[0])]],
Operator::IS => ["$column = ?", [$value[0]]],
Operator::IS_BETWEEN => ["$column BETWEEN ? AND ?", $value],
Operator::IS_GREATER_THAN => ["$column > ?", [$value[0]]],
Operator::IS_LESS_THAN => ["$column < ?", [$value[0]]],
Operator::IS_NOT => ["$column <> ?", [$value[0]]],
Operator::IS_NOT_BETWEEN => ["$column NOT BETWEEN ? AND ?", $value],
Operator::NOT_CONTAIN => ["$column NOT LIKE ?", ["%{$value[0]}%"]],
Operator::NOT_IN_LAST => static fn () => ["$column < ?", [now()->subDays($value[0])]],
};
}
private static function generateEloquentParameters(string $column, Operator $operator, array $value): array|Closure
{
return match ($operator) {
Operator::BEGINS_WITH => [$column, 'LIKE', "$value[0]%"],
Operator::ENDS_WITH => [$column, 'LIKE', "%$value[0]"],
Operator::IS => [$column, '=', $value[0]],
Operator::IS_NOT => [$column, '<>', $value[0]],
Operator::CONTAINS => [$column, 'LIKE', "%$value[0]%"],
Operator::NOT_CONTAIN => [$column, 'NOT LIKE', "%$value[0]%"],
Operator::IS_LESS_THAN => [$column, '<', $value[0]],
Operator::IS_GREATER_THAN => [$column, '>', $value[0]],
Operator::IS_BETWEEN, Operator::IS_NOT_BETWEEN => [$column, $value],
Operator::NOT_IN_LAST => static fn () => [$column, '<', now()->subDays($value[0])],
Operator::IN_LAST => static fn () => [$column, '>=', now()->subDays($value[0])],
};
}
}