<?php

namespace App\Traits;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;

trait AppliesQueryConditions
{
    /**
     * Apply query conditions (nested)
     *
     * @param Builder $query
     * @param $conditions
     * @return void
     */
    protected function applyQueryConditions(Builder $query, $conditions)
    {
        $conditions = collect($conditions)
            ->map(fn ($condition) => $condition['value']);

        foreach ($conditions as $condition) {
            if ($this->isRelationshipCondition($condition)) {
                $this->handleRelationshipCondition($query, $condition);
                continue;
            }
            if (Str::endsWith($condition['column'], '_since')) {
                $condition['column'] = str_replace('_since', '', $condition['column']);
                $condition['value'] = Carbon::now()->subDays($condition['value']);
                $condition['operator'] = $this->invertComparisonOperator($condition['operator']);
            }

            match ($condition['type']) {
                'and' => $query->where(function ($query) use ($condition) {
                    $query->when(
                        $condition['operator'] === 'in',
                        fn ($query) => $query->whereIn($condition['column'], $condition['value']),
                        fn ($query) => $query->where($condition['column'], $condition['operator'], $condition['value'])
                    )->when(
                        isset($condition['value']) && empty($condition['value']),
                        fn ($query) => $query->orWhereNull($condition['column'])
                    );
                }),
                'or' => $query->orWhere($condition['column'], $condition['operator'], $condition['value']),
                'has' => $query->whereHas($condition['column']),
                'doesnt-have' => $query->whereDoesntHave($condition['column']),
                'null' => $query->whereNull($condition['column']),
                'or-null' => $query->orWhereNull($condition['column']),
                'not-null' => $query->whereNotNull($condition['column']),
                'or-not-null' => $query->orWhereNotNull($condition['column']),
                'where-sub' => $query->where(
                    fn ($query) => $this->applyQueryConditions($query, $conditions['conditions'])
                ),
                'or-where-sub' => $query->orWhere(
                    fn ($query) => $this->applyQueryConditions($query, $conditions['conditions'])
                ),
            };
        }
    }

    /**
     * Check if the condition is for relationship object
     * Related objects are joined via . (dot)
     *
     * @param $condition
     * @return bool
     */
    protected function isRelationshipCondition(array $condition): bool
    {
        return strpos((string) $condition['column'], '.') > 0;
    }

    protected function handleRelationshipCondition(Builder $query, array $condition)
    {
        $split = explode('.', (string) $condition['column']);

        $last = array_pop($split);
        $relationship = implode('.', $split);

        if ($last === 'count') {
            $relationshipName = strtolower((string) preg_replace('/(?<!^)[A-Z]/', '_$0', $relationship)) . '_count';
            $query->withCount($relationship)
                ->having($relationshipName, $condition['operator'], $condition['value']);
        } else {
            if ($condition['operator'] == 'in') {
                $query->whereHas($relationship, fn ($related) => $related->whereIn(
                    $last,
                    $condition['value']
                ));
            } else {
                $query->whereHas($relationship, fn ($related) => $related->where(
                    $last,
                    $condition['operator'],
                    $condition['value']
                ));
            }
        }
    }

    private function invertComparisonOperator(string $operator): string
    {
        return match ($operator) {
            '>=' => '<=',
            '>' => '<',
            '<=' => '>=',
            '<' => '>',
            default => $operator,
        };
    }
}
