<?php

declare(strict_types=1);

namespace App\Actions\GraphQL;

use App\Concerns\Actions\GraphQL\ConstrainsEagerLoads;
use App\Concerns\Actions\GraphQL\FieldSelection;
use App\Concerns\Actions\GraphQL\PaginatesModels;
use App\Concerns\Actions\GraphQL\SortsModels;
use App\Contracts\GraphQL\EnumSort;
use App\GraphQL\Argument\SortArgument;
use App\GraphQL\Criteria\Sort\RelationSortCriteria;
use App\GraphQL\Schema\Types\BaseType;
use App\Rules\GraphQL\Argument\FirstArgumentRule;
use App\Scout\Criteria;
use App\Scout\Search;
use GraphQL\Type\Definition\ResolveInfo;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Validator;
use UnitEnum;

class IndexAction
{
    use ConstrainsEagerLoads;
    use FieldSelection;
    use PaginatesModels;
    use SortsModels;

    public function index(Builder $builder, array $args, BaseType $type, ResolveInfo $resolveInfo): Paginator
    {
        $this->withAggregates($builder, $args, $this->getSelection($resolveInfo), $type);

        $this->filter($builder, $args, $type);

        $this->sort($builder, $args);

        $this->constrainEagerLoads($builder, $resolveInfo, $type);

        return $this->paginate($builder, $args);
    }

    public function search(Builder $builder, array $args, BaseType $type, ResolveInfo $resolveInfo): Paginator
    {
        $criteria = new Criteria(Arr::get($args, 'search'));

        $searchBuilder = Search::getSearch($builder->getModel(), $criteria);

        $eloquentCallback = function (Builder $builder) use ($args, $type, $resolveInfo): void {
            $this->withAggregates($builder, $args, $this->getSelection($resolveInfo), $type);

            $this->filter($builder, $args, $type);

            $this->sort($builder, $args);

            $this->constrainEagerLoads($builder, $resolveInfo, $type);
        };

        // Note: First for searching must not be too high.
        $first = min(100, Arr::get($args, 'first'));
        $page = Arr::get($args, 'page', 1);

        Validator::make(['first' => $first], [
            'first' => ['required', 'integer', 'min:1', new FirstArgumentRule()],
        ])->validate();

        /** @var array<int, UnitEnum&EnumSort> $sorts */
        $sorts = Arr::get($args, SortArgument::ARGUMENT, []);

        $sortsRaw = [];
        foreach ($sorts as $sort) {
            $criterion = $sort->getSortCriteria();

            $column = $criterion->getColumn();
            $direction = $criterion->getDirection();
            $isString = $criterion->isStringField();

            if ($criterion instanceof RelationSortCriteria) {
                $sortsRaw[$column] = [
                    'direction' => $direction->value,
                    'isString' => $isString,
                    'relation' => $criterion->getRelation(),
                ];
            } else {
                $sortsRaw[$column] = [
                    'direction' => $direction,
                    'isString' => $isString,
                ];
            }
        }

        return $searchBuilder->search($eloquentCallback, $first, $page, $sortsRaw);
    }
}
