<?php

declare(strict_types=1);

namespace App\Http\Api\Criteria\Filter;

use App\Concerns\Actions\Http\Api\FiltersModels;
use App\Enums\Http\Api\Filter\BinaryLogicalOperator;
use App\Enums\Http\Api\Filter\ComparisonOperator;
use App\Http\Api\Filter\Filter;
use App\Http\Api\Query\Query;
use App\Http\Api\Schema\Schema;
use App\Http\Api\Scope\Scope;
use App\Http\Api\Scope\ScopeParser;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;

class HasCriteria extends Criteria
{
    use FiltersModels {
        filter as filterModels;
    }

    final public const string PARAM_VALUE = 'has';

    final public function __construct(
        Predicate $predicate,
        BinaryLogicalOperator $operator,
        Scope $scope,
        protected readonly int $count = 1,
    ) {
        parent::__construct($predicate, $operator, $scope);
    }

    /**
     * Get the count.
     */
    public function getCount(): int
    {
        return $this->count;
    }

    /**
     * Create a new criteria instance from query string.
     */
    public static function make(Scope $scope, string $filterParam, mixed $filterValues): static
    {
        $field = '';
        $comparisonOperator = ComparisonOperator::GTE;
        $count = 1;
        $logicalOperator = BinaryLogicalOperator::AND;

        $filterParts = Str::of($filterParam)->explode(Criteria::PARAM_SEPARATOR);

        while ($filterParts->isNotEmpty()) {
            $filterPart = $filterParts->pop();

            // Set logical operator
            if (blank($field) && BinaryLogicalOperator::unstrictCoerce($filterPart) instanceof BinaryLogicalOperator) {
                $logicalOperator = BinaryLogicalOperator::unstrictCoerce($filterPart);
                continue;
            }

            // Set count
            if (
                blank($field)
                && filter_var($filterPart, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE) !== null
            ) {
                $count = filter_var($filterPart, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE);
                continue;
            }

            // Set comparison operator
            if (blank($field) && ComparisonOperator::unstrictCoerce($filterPart) instanceof ComparisonOperator) {
                $comparisonOperator = ComparisonOperator::unstrictCoerce($filterPart);
                continue;
            }

            // Set found has param
            if (blank($field) && $filterPart === HasCriteria::PARAM_VALUE) {
                $field = $filterPart;
            }
        }

        $expression = new Expression(Str::of($filterValues)->explode(Criteria::VALUE_SEPARATOR));

        return new static(
            new Predicate(HasCriteria::PARAM_VALUE, $comparisonOperator, $expression),
            $logicalOperator,
            $scope,
            $count
        );
    }

    public function filter(Builder $builder, Filter $filter, Query $query, Schema $schema): Builder
    {
        $filterValues = $filter->getFilterValues($this->getFilterValues());

        foreach ($filterValues as $filterValue) {
            $scope = ScopeParser::parse($filterValue);
            $relationSchema = $schema->relation($filterValue);

            $builder = $builder->has(
                $filterValue,
                $this->getComparisonOperator()?->value,
                $this->count,
                $this->getLogicalOperator()->value,
                function (Builder $relationBuilder) use ($scope, $query, $relationSchema): void {
                    if ($relationSchema instanceof Schema) {
                        $this->filterModels($relationBuilder, $query, $relationSchema, $scope);
                    }
                }
            );
        }

        return $builder;
    }
}
