<?php

namespace App\Filter;

use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\QueryBuilder;

#[ApiFilter(CustomJsonOrderFilter::class)]
class CustomJsonOrderFilter extends AbstractFilter
{
    public const FILTER_NAME = "custom_json_order";

    protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void
    {
        if ($property !== self::FILTER_NAME) {
            return;
        }

        $orderData = json_decode($value, true);
        if (!is_array($orderData) || !isset($orderData['active']) || !isset($orderData['direction'])) {
            return;
        }
        $field = $orderData['active'];

        $direction = strtoupper($orderData['direction']) === 'DESC' ? 'DESC' : 'ASC';
        $rootAlias = $queryBuilder->getRootAliases()[0];

        if (array_key_exists('listColDefinition', $orderData)) {
            $listColDefinition = $orderData['listColDefinition'];
            $field = $listColDefinition['field'];
            if (array_key_exists('sortingFieldName', $listColDefinition)) {
                $field = $listColDefinition['sortingFieldName'];
            }
            $sortingSubResource = $listColDefinition['sortingSubResource'] ?? null;
            $countSortSubresource = $listColDefinition['countSortSubresource'] ?? false;
            $countSortSubresourceValue = $listColDefinition['countSortSubresourceValue'] ?? null;
            $countSortSubresourceField = $listColDefinition['countSortSubresourceField'] ?? $field;

            $countSortFilterSubResource = $listColDefinition['countSortFilterSubResource'] ?? null;
            $countSortFilterSubresourceField = $listColDefinition['countSortFilterSubresourceField'] ?? null;
            $countSortFilterSubresourceValue = $listColDefinition['countSortFilterSubresourceValue'] ?? null;

            if ($sortingSubResource) {
                $subResources = explode('.', $sortingSubResource);
                $currentAlias = $rootAlias;

                foreach ($subResources as $index => $subResource) {
                    $joinAlias = $queryNameGenerator->generateJoinAlias($subResource);
                    $queryBuilder->leftJoin(sprintf('%s.%s', $currentAlias, $subResource), $joinAlias);
                    $currentAlias = $joinAlias;
                }

                if ($countSortSubresource === true) {
                    $countAlias = $queryNameGenerator->generateParameterName('count');

                    $subQuery = $queryBuilder->getEntityManager()->createQueryBuilder()
                        ->select('COUNT(sub)')
                        ->from($resourceClass, 'main')
                        ->join(sprintf('main.%s', $sortingSubResource), 'sub');

                    if ($countSortSubresourceValue !== null && $countSortSubresourceField !== null) {
                        $paramName = $queryNameGenerator->generateParameterName($countSortSubresourceField);

                        if (is_array($countSortSubresourceValue)) {
                            $conditions = [];
                            foreach ($countSortSubresourceValue as $index => $value) {
                                $isNegation = strpos($value, '!') === 0;
                                $actualValue = $isNegation ? substr($value, 1) : $value;
                                $individualParamName = $paramName . '_' . $index;

                                if ($isNegation) {
                                    $conditions[] = sprintf('sub.%s != :%s', $countSortSubresourceField, $individualParamName);
                                } else {
                                    $conditions[] = sprintf('sub.%s = :%s', $countSortSubresourceField, $individualParamName);
                                }
                                $subQuery->setParameter($individualParamName, $actualValue);
                            }
                            $subQuery->andWhere($subQuery->expr()->orX(...$conditions));
                        } else {
                            $isNegation = strpos($countSortSubresourceValue, '!') === 0;
                            $actualValue = $isNegation ? substr($countSortSubresourceValue, 1) : $countSortSubresourceValue;

                            if ($isNegation) {
                                $subQuery->andWhere(sprintf('sub.%s != :%s', $countSortSubresourceField, $paramName));
                            } else {
                                $subQuery->andWhere(sprintf('sub.%s = :%s', $countSortSubresourceField, $paramName));
                            }
                            $subQuery->setParameter($paramName, $actualValue);
                        }
                    }

                    // Additional filtering on countSortFilterSubResource
                    if ($countSortFilterSubResource && $countSortFilterSubresourceField && $countSortFilterSubresourceValue !== null) {
                        $filterJoinAlias = $queryNameGenerator->generateJoinAlias($countSortFilterSubResource);
                        $filterParamName = $queryNameGenerator->generateParameterName($countSortFilterSubresourceField);

                        $subQuery->join(sprintf('sub.%s', $countSortFilterSubResource), $filterJoinAlias)
                            ->andWhere(sprintf('%s.%s = :%s OR %s.%s IS NULL', $filterJoinAlias, $countSortFilterSubresourceField, $filterParamName, $filterJoinAlias, $countSortFilterSubresourceField))
                            ->setParameter($filterParamName, $countSortFilterSubresourceValue);
                    }

                    $subQuery->andWhere($subQuery->expr()->eq('main.id', $rootAlias . '.id'));

                    $queryBuilder->addSelect(sprintf('(%s) as HIDDEN %s', $subQuery->getDQL(), $countAlias))
                        ->orderBy($countAlias, $direction);

                    // Bind the parameter for the main query
                    foreach ($subQuery->getParameters() as $parameter) {
                        $queryBuilder->setParameter($parameter->getName(), $parameter->getValue(), $parameter->getType());
                    }
                } else {
                    $queryBuilder->orderBy(sprintf('%s.%s', $currentAlias, $field), $direction);
                }
            } else {
                $queryBuilder->orderBy(sprintf('%s.%s', $rootAlias, $field), $direction);
            }
        } else {
            $queryBuilder->orderBy(sprintf('%s.%s', $rootAlias, $field), $direction);
        }
    }

    public function getDescription(string $resourceClass): array
    {
        return [
            self::FILTER_NAME => [
                'property' => null,
                'type' => 'string',
                'required' => false,
                'openapi' => [
                    'description' => 'Order by a specific field, optionally on nested sub-resources or by count of sub-resources. Format: { "active": "fieldName", "direction": "asc|desc", "listColDefinition": { "field": "fieldName", "sortingFieldName": "sortFieldName", "sortingSubResource": "subResource", "countSortSubresource": true, "countSortSubresourceValue": "value" or ["value1", "!value2"], "countSortSubresourceField": "fieldToCompare", "countSortFilterSubResource": "subResource", "countSortFilterSubresourceField": "field", "countSortFilterSubresourceValue": "value" } }',
                    'name' => 'custom_order',
                    'type' => 'string',
                ],
            ],
        ];
    }
}