<?php

declare(strict_types=1);

namespace BadamSoft\WooProductExporter\Filters;

use BadamSoft\WooProductExporter\Helpers\Utils;
use DateTimeImmutable;
use WP_Term;

class FilterManager {
    private const OPTION_LAST_FILTERS = 'prodexfo_last_filters';

    private ?string $brandTaxonomy = null;

    private const NUMERIC_CONDITION_OPERATORS = [ 'eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between' ];

    private const CONDITION_FIELD_MAP = [
        'regular_price' => [
            'meta_key'   => '_regular_price',
            'data_type'  => 'DECIMAL',
            'value_type' => 'decimal',
            'operators'  => self::NUMERIC_CONDITION_OPERATORS,
        ],
        'sale_price'    => [
            'meta_key'   => '_sale_price',
            'data_type'  => 'DECIMAL',
            'value_type' => 'decimal',
            'operators'  => self::NUMERIC_CONDITION_OPERATORS,
        ],
        'stock_quantity' => [
            'meta_key'   => '_stock',
            'data_type'  => 'NUMERIC',
            'value_type' => 'integer',
            'operators'  => self::NUMERIC_CONDITION_OPERATORS,
        ],
        'total_sales' => [
            'meta_key'   => 'total_sales',
            'data_type'  => 'NUMERIC',
            'value_type' => 'integer',
            'operators'  => self::NUMERIC_CONDITION_OPERATORS,
        ],
        'review_count' => [
            'meta_key'   => '_wc_review_count',
            'data_type'  => 'NUMERIC',
            'value_type' => 'integer',
            'operators'  => self::NUMERIC_CONDITION_OPERATORS,
        ],
    ];

    /**
     * Sanitize filters from the request payload.
     *
     * @param array<string, mixed> $request
     * @return array<string, mixed>
     */
    public function sanitize_filters_from_request( array $request ): array {
        $filters = $this->getFilterDefaults();

        $filters['category'] = $this->sanitizePositiveIntFromRequest( $request, 'filter_category' );
        $filters['brand']    = $this->sanitizePositiveIntFromRequest( $request, 'filter_brand' );

        $filters['price']['regular'] = $this->sanitizeRangeFromRequest( $request, 'filter_regular_price_min', 'filter_regular_price_max', 'decimal' );
        $filters['price']['sale']    = $this->sanitizeRangeFromRequest( $request, 'filter_sale_price_min', 'filter_sale_price_max', 'decimal' );

        $filters['stock']['min']            = $this->sanitizeNumericValueFromRequest( $request, 'filter_stock_min', 'integer' );
        $filters['stock']['max']            = $this->sanitizeNumericValueFromRequest( $request, 'filter_stock_max', 'integer' );
        $filters['stock']['only_in_stock']  = $this->sanitizeCheckboxFromRequest( $request, 'filter_stock_only_in_stock' );
        $filters['stock']['only_zero']      = $this->sanitizeCheckboxFromRequest( $request, 'filter_stock_only_zero' );

        $filters['date_created']  = $this->sanitizeDateRangeFromRequest( $request, 'filter_created_from', 'filter_created_to' );
        $filters['date_modified'] = $this->sanitizeDateRangeFromRequest( $request, 'filter_modified_from', 'filter_modified_to' );

        $filters['discount_mode'] = $this->sanitizeChoiceFromRequest( $request, 'filter_discount_mode', [ '', 'discounted', 'non_discounted' ] );
        $filters['image_mode']    = $this->sanitizeChoiceFromRequest( $request, 'filter_image_mode', [ '', 'with_image', 'without_image' ] );
        $filters['reviews_mode']  = $this->sanitizeChoiceFromRequest( $request, 'filter_reviews_mode', [ '', 'with_reviews', 'without_reviews' ] );

        $filters['description_search'] = isset( $request['filter_description_search'] )
            ? sanitize_text_field( wp_unslash( (string) $request['filter_description_search'] ) )
            : '';

        $filters['exclude_categories'] = $this->sanitizeIdArrayFromRequest( $request, 'filter_exclude_categories' );
        $filters['exclude_tags']       = $this->sanitizeIdArrayFromRequest( $request, 'filter_exclude_tags' );

        $filters['condition_groups'] = $this->sanitizeConditionGroups( $request['filter_condition_groups'] ?? null );

        return $filters;
    }

    /**
     * Persist last-used export filters.
     *
     * @param array<string, mixed> $filters
     */
    public function remember_filters( array $filters ): void {
        update_option( self::OPTION_LAST_FILTERS, $filters );
    }

    /**
     * Retrieve saved filters merged with defaults.
     *
     * @return array<string, mixed>
     */
    public function get_saved_filters(): array {
        $defaults = $this->getFilterDefaults();
        $saved    = get_option( self::OPTION_LAST_FILTERS, [] );

        if ( ! is_array( $saved ) ) {
            return $defaults;
        }

        $filters = array_replace_recursive( $defaults, $saved );

        $filters['category'] = isset( $filters['category'] ) ? (int) $filters['category'] : 0;
        $filters['brand']    = isset( $filters['brand'] ) ? (int) $filters['brand'] : 0;

        $filters['exclude_categories'] = array_map( 'intval', (array) ( $filters['exclude_categories'] ?? [] ) );
        $filters['exclude_tags']       = array_map( 'intval', (array) ( $filters['exclude_tags'] ?? [] ) );

        return $filters;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function get_category_choices(): array {
        $terms = get_terms(
            [
                'taxonomy'   => 'product_cat',
                'hide_empty' => false,
            ]
        );

        if ( is_wp_error( $terms ) ) {
            return [];
        }

        return array_map(
            static function ( WP_Term $term ): array {
                return [
                    'id'   => $term->term_id,
                    'name' => Utils::format_term_with_hierarchy( $term ),
                ];
            },
            $terms
        );
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function get_brand_choices(): array {
        $taxonomy = $this->get_brand_taxonomy();

        if ( ! $taxonomy ) {
            return [];
        }

        $terms = get_terms(
            [
                'taxonomy'   => $taxonomy,
                'hide_empty' => false,
            ]
        );

        if ( is_wp_error( $terms ) ) {
            return [];
        }

        return array_map(
            static function ( WP_Term $term ): array {
                return [
                    'id'   => $term->term_id,
                    'name' => $term->name,
                ];
            },
            $terms
        );
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function get_tag_choices(): array {
        $terms = get_terms(
            [
                'taxonomy'   => 'product_tag',
                'hide_empty' => false,
            ]
        );

        if ( is_wp_error( $terms ) ) {
            return [];
        }

        return array_map(
            static function ( WP_Term $term ): array {
                return [
                    'id'   => $term->term_id,
                    'name' => $term->name,
                ];
            },
            $terms
        );
    }

    /**
     * Detect and cache the product brand taxonomy slug, if any.
     *
     * @return string|null
     */
    public function get_brand_taxonomy(): ?string {
        if ( null !== $this->brandTaxonomy ) {
            return '' === $this->brandTaxonomy ? null : $this->brandTaxonomy;
        }

        $detected = $this->detectBrandTaxonomy();

        /**
         * Allow overriding the detected brand taxonomy slug.
         *
         * @param string|null $detected Current detected taxonomy.
         */
        $detected = apply_filters( 'prodexfo_brand_taxonomy', $detected );

        if ( $detected && taxonomy_exists( $detected ) ) {
            $this->brandTaxonomy = $detected;

            return $this->brandTaxonomy;
        }

        if ( did_action( 'init' ) ) {
            $this->brandTaxonomy = '';
        }

        return null;
    }

    private function detectBrandTaxonomy(): ?string {
        $candidates = apply_filters(
            'prodexfo_brand_taxonomy_candidates',
            [
                'pa_brand',
                'pa_brend',
                'pa_marka',
                'product_brand',
                'product_brands',
                'wc_product_brand',
                'yith_product_brand',
                'pwb-brand',
                'brands',
                'brand',
            ]
        );

        foreach ( $candidates as $taxonomy ) {
            if ( taxonomy_exists( $taxonomy ) ) {
                return $taxonomy;
            }
        }

        if ( function_exists( 'wc_get_attribute_taxonomies' ) ) {
            foreach ( wc_get_attribute_taxonomies() as $taxonomy ) {
                $maybe_brand = wc_attribute_taxonomy_name( $taxonomy->attribute_name );
                $label       = $taxonomy->attribute_label ?? '';

                if ( $this->looksLikeBrandTaxonomy( $maybe_brand ) || $this->looksLikeBrandTaxonomy( $label ) ) {
                    if ( taxonomy_exists( $maybe_brand ) ) {
                        return $maybe_brand;
                    }
                }
            }
        }

        return null;
    }

    private function looksLikeBrandTaxonomy( string $value ): bool {
        $value      = strtolower( $value );
        $needles    = [ 'brand', 'brend', 'marka', 'marca', 'marque', "\u{0431}\u{0440}\u{0435}\u{043D}\u{0434}" ];
        $hasMatches = array_filter(
            $needles,
            static function ( string $needle ) use ( $value ): bool {
                return false !== strpos( $value, $needle );
            }
        );

        return ! empty( $hasMatches );
    }

    /**
     * @return array<string, array<string, mixed>>
     */
    public function get_condition_field_map(): array {
        return self::CONDITION_FIELD_MAP;
    }

    /**
     * Build tax query args for current filters.
     *
     * @param array<string, mixed> $filters
     * @return array<int, array<string, mixed>>
     */
    public function build_tax_query( array $filters ): array {
        $tax_query = [];

        if ( ! empty( $filters['category'] ) ) {
            $tax_query[] = [
                'taxonomy' => 'product_cat',
                'field'    => 'term_id',
                'terms'    => (int) $filters['category'],
            ];
        }

        $brandTaxonomy = $this->get_brand_taxonomy();

        if ( $brandTaxonomy && ! empty( $filters['brand'] ) ) {
            $tax_query[] = [
                'taxonomy' => $brandTaxonomy,
                'field'    => 'term_id',
                'terms'    => (int) $filters['brand'],
            ];
        }

        if ( ! empty( $filters['exclude_categories'] ) ) {
            $tax_query[] = [
                'taxonomy' => 'product_cat',
                'field'    => 'term_id',
                'terms'    => array_map( 'intval', (array) $filters['exclude_categories'] ),
                'operator' => 'NOT IN',
            ];
        }

        if ( ! empty( $filters['exclude_tags'] ) ) {
            $tax_query[] = [
                'taxonomy' => 'product_tag',
                'field'    => 'term_id',
                'terms'    => array_map( 'intval', (array) $filters['exclude_tags'] ),
                'operator' => 'NOT IN',
            ];
        }

        if ( count( $tax_query ) > 1 ) {
            $tax_query['relation'] = 'AND';
        }

        return $tax_query;
    }

    /**
     * @param array<string, mixed> $filters
     * @return array<int|string, mixed>
     */
    public function build_meta_query( array $filters ): array {
        $meta_query       = [];
        $price_filters    = $filters['price'] ?? [];
        $stock_filters    = $filters['stock'] ?? [];
        $discount_mode    = $filters['discount_mode'] ?? '';
        $image_mode       = $filters['image_mode'] ?? '';
        $reviews_mode     = $filters['reviews_mode'] ?? '';
        $condition_groups = $filters['condition_groups'] ?? [];

        if ( isset( $price_filters['regular'] ) ) {
            $this->appendRangeMetaClause( $meta_query, '_regular_price', 'DECIMAL', $price_filters['regular'] );
        }

        if ( isset( $price_filters['sale'] ) ) {
            $this->appendRangeMetaClause( $meta_query, '_sale_price', 'DECIMAL', $price_filters['sale'] );
        }

        if ( ! empty( $stock_filters ) ) {
            $this->appendRangeMetaClause( $meta_query, '_stock', 'NUMERIC', [
                'min' => $stock_filters['min'] ?? null,
                'max' => $stock_filters['max'] ?? null,
            ] );

            $only_zero     = ! empty( $stock_filters['only_zero'] );
            $only_in_stock = ! empty( $stock_filters['only_in_stock'] ) && ! $only_zero;

            if ( $only_in_stock ) {
                $meta_query[] = [
                    'key'     => '_stock_status',
                    'value'   => 'instock',
                    'compare' => '=',
                ];
            }

            if ( $only_zero ) {
                $meta_query[] = [
                    'relation' => 'OR',
                    [
                        'key'     => '_stock',
                        'value'   => 0,
                        'type'    => 'NUMERIC',
                        'compare' => '<=',
                    ],
                    [
                        'key'     => '_stock_status',
                        'value'   => 'outofstock',
                        'compare' => '=',
                    ],
                ];
            }
        }

        if ( 'discounted' === $discount_mode ) {
            $meta_query[] = [
                'key'     => '_sale_price',
                'value'   => 0,
                'type'    => 'DECIMAL',
                'compare' => '>',
            ];
        } elseif ( 'non_discounted' === $discount_mode ) {
            $meta_query[] = [
                'relation' => 'OR',
                [
                    'key'     => '_sale_price',
                    'compare' => 'NOT EXISTS',
                ],
                [
                    'key'     => '_sale_price',
                    'value'   => '',
                    'compare' => '=',
                ],
                [
                    'key'     => '_sale_price',
                    'value'   => 0,
                    'type'    => 'DECIMAL',
                    'compare' => '<=',
                ],
            ];
        }

        if ( 'with_image' === $image_mode ) {
            $meta_query[] = [
                'key'     => '_thumbnail_id',
                'type'    => 'NUMERIC',
                'value'   => 0,
                'compare' => '>',
            ];
        } elseif ( 'without_image' === $image_mode ) {
            $meta_query[] = [
                'relation' => 'OR',
                [
                    'key'     => '_thumbnail_id',
                    'compare' => 'NOT EXISTS',
                ],
                [
                    'key'     => '_thumbnail_id',
                    'value'   => 0,
                    'compare' => '=',
                ],
            ];
        }

        if ( 'with_reviews' === $reviews_mode ) {
            $meta_query[] = [
                'key'     => '_wc_review_count',
                'value'   => 0,
                'type'    => 'NUMERIC',
                'compare' => '>',
            ];
        } elseif ( 'without_reviews' === $reviews_mode ) {
            $meta_query[] = [
                'relation' => 'OR',
                [
                    'key'     => '_wc_review_count',
                    'compare' => 'NOT EXISTS',
                ],
                [
                    'key'     => '_wc_review_count',
                    'value'   => 0,
                    'type'    => 'NUMERIC',
                    'compare' => '<=',
                ],
            ];
        }

        $conditions_clause = $this->buildConditionGroupsMetaQuery( $condition_groups );

        if ( $conditions_clause ) {
            $meta_query[] = $conditions_clause;
        }

        if ( count( $meta_query ) > 1 && ! isset( $meta_query['relation'] ) ) {
            $meta_query['relation'] = 'AND';
        }

        return $meta_query;
    }

    /**
     * @param array<string, mixed> $filters
     * @return array<int|string, mixed>
     */
    public function build_date_query( array $filters ): array {
        $date_query     = [];
        $created_range  = $filters['date_created'] ?? [];
        $modified_range = $filters['date_modified'] ?? [];

        $created_clause = $this->buildDateClause( $created_range, 'post_date' );

        if ( $created_clause ) {
            $date_query[] = $created_clause;
        }

        $modified_clause = $this->buildDateClause( $modified_range, 'post_modified' );

        if ( $modified_clause ) {
            $date_query[] = $modified_clause;
        }

        if ( count( $date_query ) > 1 && ! isset( $date_query['relation'] ) ) {
            $date_query['relation'] = 'AND';
        }

        return $date_query;
    }

    /**
     * @return array<string, mixed>
     */
    private function getFilterDefaults(): array {
        return [
            'category'            => 0,
            'brand'               => 0,
            'price'               => [
                'regular' => [ 'min' => null, 'max' => null ],
                'sale'    => [ 'min' => null, 'max' => null ],
            ],
            'stock'               => [
                'min'           => null,
                'max'           => null,
                'only_in_stock' => false,
                'only_zero'     => false,
            ],
            'date_created'        => [ 'from' => null, 'to' => null ],
            'date_modified'       => [ 'from' => null, 'to' => null ],
            'discount_mode'       => '',
            'image_mode'          => '',
            'reviews_mode'        => '',
            'description_search'  => '',
            'exclude_categories'  => [],
            'exclude_tags'        => [],
            'condition_groups'    => [
                'relation' => 'AND',
                'groups'   => [],
            ],
        ];
    }

    private function sanitizePositiveIntFromRequest( array $request, string $key ): int {
        if ( ! isset( $request[ $key ] ) ) {
            return 0;
        }

        return max( 0, (int) wp_unslash( $request[ $key ] ) );
    }

    private function sanitizeRangeFromRequest( array $request, string $min_key, string $max_key, string $value_type ): array {
        $range = [
            'min' => $this->sanitizeNumericValueFromRequest( $request, $min_key, $value_type ),
            'max' => $this->sanitizeNumericValueFromRequest( $request, $max_key, $value_type ),
        ];

        if ( null !== $range['min'] && null !== $range['max'] && $range['min'] > $range['max'] ) {
            [ $range['min'], $range['max'] ] = [ $range['max'], $range['min'] ];
        }

        return $range;
    }

    private function sanitizeNumericValueFromRequest( array $request, string $key, string $value_type ): ?float {
        if ( ! isset( $request[ $key ] ) ) {
            return null;
        }

        return $this->sanitizeNumericScalar( wp_unslash( $request[ $key ] ), $value_type );
    }

    private function sanitizeNumericScalar( $value, string $value_type ): ?float {
        if ( is_array( $value ) ) {
            $value = reset( $value );
        }

        if ( ! is_scalar( $value ) ) {
            return null;
        }

        $value = (string) $value;
        $value = str_replace( ',', '.', $value );
        $value = trim( $value );

        if ( '' === $value || ! is_numeric( $value ) ) {
            return null;
        }

        $number = (float) $value;

        if ( 'integer' === $value_type ) {
            return (float) (int) round( $number );
        }

        return $number;
    }

    private function sanitizeCheckboxFromRequest( array $request, string $key ): bool {
        if ( ! isset( $request[ $key ] ) ) {
            return false;
        }

        $value = $request[ $key ];

        if ( is_array( $value ) ) {
            $value = reset( $value );
        }

        return in_array( (string) $value, [ '1', 'true', 'on' ], true );
    }

    private function sanitizeDateRangeFromRequest( array $request, string $from_key, string $to_key ): array {
        return [
            'from' => $this->sanitizeDateValue( $request[ $from_key ] ?? null ),
            'to'   => $this->sanitizeDateValue( $request[ $to_key ] ?? null ),
        ];
    }

    private function sanitizeDateValue( $value ): ?string {
        if ( null === $value ) {
            return null;
        }

        if ( is_array( $value ) ) {
            $value = reset( $value );
        }

        $value = trim( (string) $value );

        if ( '' === $value ) {
            return null;
        }

        try {
            $date = new DateTimeImmutable( $value );
        } catch ( \Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
            return null;
        }

        return $date->format( 'Y-m-d' );
    }

    private function sanitizeChoiceFromRequest( array $request, string $key, array $choices ): string {
        if ( ! isset( $request[ $key ] ) ) {
            return '';
        }

        $value = sanitize_key( wp_unslash( (string) $request[ $key ] ) );

        return in_array( $value, $choices, true ) ? $value : '';
    }

    private function sanitizeIdArrayFromRequest( array $request, string $key ): array {
        if ( ! isset( $request[ $key ] ) ) {
            return [];
        }

        $values = (array) wp_unslash( $request[ $key ] );

        $ids = array_map( 'intval', $values );
        $ids = array_filter( $ids, static function ( int $id ): bool {
            return $id > 0;
        } );

        return array_values( array_unique( $ids ) );
    }

    private function sanitizeConditionGroups( $raw ): array {
        $defaults = [
            'relation' => 'AND',
            'groups'   => [],
        ];

        if ( empty( $raw ) ) {
            return $defaults;
        }

        if ( is_string( $raw ) ) {
            $decoded = json_decode( wp_unslash( $raw ), true );
        } elseif ( is_array( $raw ) ) {
            $decoded = $raw;
        } else {
            return $defaults;
        }

        if ( ! is_array( $decoded ) ) {
            return $defaults;
        }

        $relation = $this->normalizeConditionRelation( $decoded['relation'] ?? 'AND' );
        $groups   = [];
        $input    = isset( $decoded['groups'] ) && is_array( $decoded['groups'] ) ? $decoded['groups'] : [];

        foreach ( $input as $group ) {
            if ( ! is_array( $group ) ) {
                continue;
            }

            $group_relation = $this->normalizeConditionRelation( $group['relation'] ?? 'AND' );
            $raw_conditions = isset( $group['conditions'] ) && is_array( $group['conditions'] ) ? $group['conditions'] : [];

            $conditions = [];

            foreach ( $raw_conditions as $condition ) {
                if ( ! is_array( $condition ) ) {
                    continue;
                }

                $sanitized = $this->sanitizeConditionDefinition( $condition );

                if ( $sanitized ) {
                    $conditions[] = $sanitized;
                }
            }

            if ( ! empty( $conditions ) ) {
                $groups[] = [
                    'relation'   => $group_relation,
                    'conditions' => $conditions,
                ];
            }
        }

        return [
            'relation' => $relation,
            'groups'   => $groups,
        ];
    }

    private function normalizeConditionRelation( string $value ): string {
        $value = strtoupper( $value );

        return in_array( $value, [ 'AND', 'OR' ], true ) ? $value : 'AND';
    }

    private function sanitizeConditionDefinition( array $condition ): ?array {
        $field = isset( $condition['field'] ) ? sanitize_key( (string) $condition['field'] ) : '';

        if ( ! isset( self::CONDITION_FIELD_MAP[ $field ] ) ) {
            return null;
        }

        $definition = self::CONDITION_FIELD_MAP[ $field ];
        $operator   = isset( $condition['operator'] ) ? strtolower( (string) $condition['operator'] ) : '';

        if ( ! in_array( $operator, $definition['operators'], true ) ) {
            return null;
        }

        $value    = $this->sanitizeNumericScalar( $condition['value'] ?? null, $definition['value_type'] );
        $value_to = null;

        if ( 'between' === $operator ) {
            $value_to = $this->sanitizeNumericScalar( $condition['value_to'] ?? null, $definition['value_type'] );

            if ( null === $value || null === $value_to ) {
                return null;
            }

            if ( $value > $value_to ) {
                [ $value, $value_to ] = [ $value_to, $value ];
            }
        } else {
            if ( null === $value ) {
                return null;
            }
        }

        return [
            'field'      => $field,
            'operator'   => $operator,
            'value'      => $value,
            'value_to'   => $value_to,
            'meta_key'   => $definition['meta_key'],
            'data_type'  => $definition['data_type'],
            'value_type' => $definition['value_type'],
        ];
    }

    private function appendRangeMetaClause( array &$meta_query, string $meta_key, string $type, array $range ): void {
        $min = $range['min'] ?? null;
        $max = $range['max'] ?? null;

        if ( null === $min && null === $max ) {
            return;
        }

        if ( null !== $min && null !== $max ) {
            $meta_query[] = [
                'key'     => $meta_key,
                'value'   => [ $min, $max ],
                'type'    => $type,
                'compare' => 'BETWEEN',
            ];

            return;
        }

        $value   = null !== $min ? $min : $max;
        $compare = null !== $min ? '>=' : '<=';

        $meta_query[] = [
            'key'     => $meta_key,
            'value'   => $value,
            'type'    => $type,
            'compare' => $compare,
        ];
    }

    private function buildConditionGroupsMetaQuery( array $definition ): ?array {
        if ( empty( $definition['groups'] ) || ! is_array( $definition['groups'] ) ) {
            return null;
        }

        $relation = $this->normalizeConditionRelation( $definition['relation'] ?? 'AND' );
        $clauses  = [];

        foreach ( $definition['groups'] as $group ) {
            $group_clause = $this->buildConditionGroupClause( $group );

            if ( $group_clause ) {
                $clauses[] = $group_clause;
            }
        }

        if ( empty( $clauses ) ) {
            return null;
        }

        if ( 1 === count( $clauses ) ) {
            return $clauses[0];
        }

        $wrapper = [ 'relation' => $relation ];

        foreach ( $clauses as $clause ) {
            $wrapper[] = $clause;
        }

        return $wrapper;
    }

    private function buildConditionGroupClause( $group ): ?array {
        if ( ! is_array( $group ) || empty( $group['conditions'] ) || ! is_array( $group['conditions'] ) ) {
            return null;
        }

        $relation = $this->normalizeConditionRelation( $group['relation'] ?? 'AND' );
        $clauses  = [];

        foreach ( $group['conditions'] as $condition ) {
            if ( ! is_array( $condition ) ) {
                continue;
            }

            $clause = $this->buildConditionClause( $condition );

            if ( $clause ) {
                $clauses[] = $clause;
            }
        }

        if ( empty( $clauses ) ) {
            return null;
        }

        $group_clause = [ 'relation' => $relation ];

        foreach ( $clauses as $clause ) {
            $group_clause[] = $clause;
        }

        return $group_clause;
    }

    private function buildConditionClause( array $condition ): ?array {
        if ( empty( $condition['meta_key'] ) || empty( $condition['operator'] ) ) {
            return null;
        }

        $operator_map = [
            'eq'      => '=',
            'neq'     => '!=',
            'gt'      => '>',
            'gte'     => '>=',
            'lt'      => '<',
            'lte'     => '<=',
            'between' => 'BETWEEN',
        ];

        $operator = $condition['operator'];

        if ( ! isset( $operator_map[ $operator ] ) ) {
            return null;
        }

        $clause = [
            'key'     => $condition['meta_key'],
            'type'    => $condition['data_type'] ?? 'CHAR',
            'compare' => $operator_map[ $operator ],
        ];

        if ( 'between' === $operator ) {
            if ( ! isset( $condition['value'], $condition['value_to'] ) ) {
                return null;
            }

            $clause['value'] = [ $condition['value'], $condition['value_to'] ];
        } else {
            if ( ! isset( $condition['value'] ) ) {
                return null;
            }

            $clause['value'] = $condition['value'];
        }

        return $clause;
    }

    private function buildDateClause( $range, string $column ): ?array {
        if ( ! is_array( $range ) ) {
            return null;
        }

        $from = $range['from'] ?? null;
        $to   = $range['to'] ?? null;

        if ( ! $from && ! $to ) {
            return null;
        }

        $clause = [
            'column'    => $column,
            'inclusive' => true,
        ];

        if ( $from ) {
            $clause['after'] = $from;
        }

        if ( $to ) {
            $clause['before'] = $to;
        }

        return $clause;
    }
}
