<?php
/**
 * Post Query Manager
 *
 * Centralizes all post query logic (controls registration + WP_Query args building)
 * so that every widget (Post Loop Grid, Post Timeline, etc.) shares one place.
 *
 * Usage in any widget:
 *   1. Call  Softech_Post_Query_Manager::register_controls( $this );
 *      inside register_controls() to add the query section.
 *
 *   2. Call  Softech_Post_Query_Manager::build_query_args( $settings, $paged )
 *      to get the WP_Query args array.
 *
 * File location: includes/class-post-query-manager.php
 *
 * @package Softech_Addons_For_Elementor
 */

namespace Softech_Addons_for_Elementor\Includes;

use Elementor\Controls_Manager;

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class Softech_Post_Query_Manager {

    /** Transient lifetime – 12 hours */
    const CACHE_EXPIRATION = 43200;

    /** Post types always hidden from the selector */
    const EXCLUDED_POST_TYPES = [
        'elementor_library',
        'attachment',
        'revision',
        'nav_menu_item',
        'custom_css',
        'customize_changeset',
        'oembed_cache',
        'user_request',
        'wp_block',
    ];

    /**
     * Post types that do NOT support standard WP categories/tags.
     * Categories, Exclude Categories, and Tags controls are hidden when
     * one of these is selected in post_only mode.
     * Extend this list with any CPT slugs that use their own custom taxonomies.
     */
    const POST_TYPES_WITHOUT_CATEGORIES = [
        'page',
    ];

    // ─────────────────────────────────────────────────────────────────────
    // 1. CONTROL REGISTRATION
    // ─────────────────────────────────────────────────────────────────────

    /**
     * Register the full "Query" controls section inside an Elementor widget.
     *
     * @param \Elementor\Widget_Base $widget  The widget instance.
     * @param array                  $options Optional overrides:
     *   'source_control' => (string) name of the widget's source control
     *                       whose value must be 'dynamic' to show the section.
     *                       Pass '' to always show (default: '').
     *   'section_id'     => (string) Elementor section ID.
     *   'section_label'  => (string) Translated label.
     *   'mode'           => (string) 'full' or 'post_only'.
     */
    public static function register_controls(
        \Elementor\Widget_Base $widget,
        array $options = []
    ): void {

        $source_control = $options['source_control'] ?? '';
        $section_id     = $options['section_id']     ?? 'section_post_query';
        $section_label  = $options['section_label']  ?? esc_html__( 'Query', 'softech-addons-for-elementor' );
        $mode           = $options['mode']            ?? 'full'; // 'full' or 'post_only'

        // ── Section open ─────────────────────────────────────────────────
        $section_args = [
            'label' => $section_label,
            'tab'   => Controls_Manager::TAB_CONTENT,
        ];
        if ( $source_control !== '' ) {
            $section_args['condition'] = [ $source_control => 'dynamic' ];
        }
        $widget->start_controls_section( $section_id, $section_args );

        // ── Mode-specific: Post Type control + condition vars ─────────────
        if ( $mode === 'full' ) {

            $widget->add_control( 'choose_template_type', [
                'label'   => esc_html__( 'Source Type', 'softech-addons-for-elementor' ),
                'type'    => Controls_Manager::SELECT,
                'options' => self::get_template_type_options(),
                'default' => 'post',
            ] );

            $widget->add_control( 'source_post_taxonomy', [
                'label'     => esc_html__( 'Source Taxonomy', 'softech-addons-for-elementor' ),
                'type'      => Controls_Manager::SELECT,
                'default'   => 'category',
                'options'   => self::get_taxonomies_for_select(),
                'condition' => [ 'choose_template_type' => self::get_taxonomy_type_keys() ],
            ] );

            // In full mode the post-type dropdown IS the condition guard.
            $post_type_condition = [ 'choose_template_type' => self::get_post_type_keys() ];

            // No extra taxonomy condition in full mode — the taxonomy _type variants
            // are separate options so category/tag controls only show for plain post types.
            $taxonomy_condition = [];

        } else {

            // post_only mode — simple flat post-type dropdown.
            $widget->add_control( 'post_type', [
                'label'   => esc_html__( 'Post Type', 'softech-addons-for-elementor' ),
                'type'    => Controls_Manager::SELECT,
                'options' => self::get_post_types(),
                'default' => 'post',
            ] );

            $post_type_condition = []; // always visible in post_only mode

            // Hide categories/tags when post type doesn't support them (e.g. 'page').
            // Add more slugs to POST_TYPES_WITHOUT_CATEGORIES as needed.
            $taxonomy_condition = [ 'post_type!' => self::POST_TYPES_WITHOUT_CATEGORIES ];
        }

        // ── Selection mode ────────────────────────────────────────────────
        $widget->add_control( 'selection', [
            'label'     => esc_html__( 'Selection', 'softech-addons-for-elementor' ),
            'type'      => Controls_Manager::SELECT,
            'default'   => 'dynamic',
            'options'   => [
                'dynamic' => esc_html__( 'Dynamic (Auto)', 'softech-addons-for-elementor' ),
                'manual'  => esc_html__( 'Manual Pick', 'softech-addons-for-elementor' ),
            ],
            'condition' => $post_type_condition,
        ] );

        // ── Manual pick ───────────────────────────────────────────────────
        $widget->add_control( 'manual_posts', [
            'label'       => esc_html__( 'Select Posts', 'softech-addons-for-elementor' ),
            'type'        => Controls_Manager::SELECT2,
            'multiple'    => true,
            'label_block' => true,
            'options'     => self::get_posts_for_select(),
            'condition'   => array_merge( $post_type_condition, [ 'selection' => 'manual' ] ),
        ] );

        // ── Taxonomy filters ──────────────────────────────────────────────
        // All three share $taxonomy_condition so they disappear for post types
        // that don't use standard WP categories (e.g. 'page').

        $widget->add_control( 'categories', [
            'label'       => esc_html__( 'Filter by Categories', 'softech-addons-for-elementor' ),
            'type'        => Controls_Manager::SELECT2,
            'multiple'    => true,
            'label_block' => true,
            'options'     => self::get_categories_for_select(),
            'condition'   => array_merge( $post_type_condition, $taxonomy_condition, [ 'selection' => 'dynamic' ] ),
        ] );

        $widget->add_control( 'exclude_categories', [
            'label'       => esc_html__( 'Exclude Categories', 'softech-addons-for-elementor' ),
            'type'        => Controls_Manager::SELECT2,
            'multiple'    => true,
            'label_block' => true,
            'options'     => self::get_categories_for_select(),
            'condition'   => array_merge( $post_type_condition, $taxonomy_condition, [ 'selection' => 'dynamic' ] ),
        ] );
        

       $widget->add_control( 'manual_tags', [
            'label'       => esc_html__( 'Filter by Tags', 'softech-addons-for-elementor' ),
            'type'        => Controls_Manager::SELECT2,
            'multiple'    => true,
            'label_block' => true,
            'options'     => self::get_tags_for_select(),
            'condition'   => array_merge( $post_type_condition, $taxonomy_condition, [ 'selection' => 'dynamic' ] ),
        ] );

 $widget->add_control( 'exclude_tags', [
            'label'       => esc_html__( 'Exclude by Tags', 'softech-addons-for-elementor' ),
            'type'        => Controls_Manager::SELECT2,
            'multiple'    => true,
            'label_block' => true,
            'options'     => self::get_tags_for_select(),
            'condition'   => array_merge( $post_type_condition, $taxonomy_condition, [ 'selection' => 'dynamic' ] ),
        ] );


        // ── Dynamic ordering / offset ─────────────────────────────────────
        $widget->add_control( 'offset', [
            'label'       => esc_html__( 'Offset', 'softech-addons-for-elementor' ),
            'description' => esc_html__( 'Note: offset is incompatible with pagination.', 'softech-addons-for-elementor' ),
            'type'        => Controls_Manager::NUMBER,
            'default'     => 0,
            'min'         => 0,
            'condition'   => array_merge( $post_type_condition, [ 'selection' => 'dynamic' ] ),
        ] );

        $widget->add_control( 'orderby', [
            'label'     => esc_html__( 'Order By', 'softech-addons-for-elementor' ),
            'type'      => Controls_Manager::SELECT,
            'default'   => 'date',
            'options'   => [
                'date'           => esc_html__( 'Date', 'softech-addons-for-elementor' ),
                'title'          => esc_html__( 'Title', 'softech-addons-for-elementor' ),
                'modified'       => esc_html__( 'Modified Date', 'softech-addons-for-elementor' ),
                'comment_count'  => esc_html__( 'Comment Count', 'softech-addons-for-elementor' ),
                'rand'           => esc_html__( 'Random', 'softech-addons-for-elementor' ),
                'meta_value'     => esc_html__( 'Meta Value', 'softech-addons-for-elementor' ),
                'meta_value_num' => esc_html__( 'Meta Value (Numeric)', 'softech-addons-for-elementor' ),
            ],
            'condition' => array_merge( $post_type_condition, [ 'selection' => 'dynamic' ] ),
        ] );

        $widget->add_control( 'order', [
            'label'     => esc_html__( 'Order', 'softech-addons-for-elementor' ),
            'type'      => Controls_Manager::SELECT,
            'default'   => 'DESC',
            'options'   => [
                'DESC' => esc_html__( 'Descending', 'softech-addons-for-elementor' ),
                'ASC'  => esc_html__( 'Ascending', 'softech-addons-for-elementor' ),
            ],
            'condition' => array_merge( $post_type_condition, [ 'selection' => 'dynamic' ] ),
        ] );

        // ── Meta Query ────────────────────────────────────────────────────
        // ── Meta Query ────────────────────────────────────────────────────
$widget->add_control( 'meta_query_heading', [
    'label'     => esc_html__( 'Meta Query', 'softech-addons-for-elementor' ),
    'type'      => Controls_Manager::HEADING,
    'separator' => 'before',
    'condition' => array_merge( $post_type_condition, [ 'selection' => 'dynamic' ] ),
] );

$widget->add_control( 'meta_query_relation', [
    'label'     => esc_html__( 'Relation Between Conditions', 'softech-addons-for-elementor' ),
    'type'      => Controls_Manager::SELECT,
    'default'   => 'AND',
    'options'   => [
        'AND' => esc_html__( 'AND (all must match)', 'softech-addons-for-elementor' ),
        'OR'  => esc_html__( 'OR (any must match)', 'softech-addons-for-elementor' ),
    ],
    'condition' => array_merge( $post_type_condition, [ 'selection' => 'dynamic' ] ),
] );

// Meta Query Repeater
$meta_repeater = new \Elementor\Repeater();

$meta_repeater->add_control( 'meta_key', [
    'label'       => esc_html__( 'Meta Key', 'softech-addons-for-elementor' ),
    'type'        => Controls_Manager::TEXT,
    'placeholder' => '_my_custom_field',
    'label_block' => true,
] );

$meta_repeater->add_control( 'meta_compare', [
    'label'   => esc_html__( 'Compare', 'softech-addons-for-elementor' ),
    'type'    => Controls_Manager::SELECT,
    'default' => '=',
    'options' => [
        '='          => '= (Equal)',
        '!='         => '!= (Not Equal)',
        '>'          => '> (Greater Than)',
        '>='         => '>= (Greater or Equal)',
        '<'          => '< (Less Than)',
        '<='         => '<= (Less or Equal)',
        'LIKE'       => 'LIKE',
        'NOT LIKE'   => 'NOT LIKE',
        'IN'         => 'IN (comma-separated values)',
        'NOT IN'     => 'NOT IN (comma-separated values)',
        'BETWEEN'    => 'BETWEEN (two comma-separated values)',
        'NOT BETWEEN'=> 'NOT BETWEEN',
        'EXISTS'     => 'EXISTS (key exists)',
        'NOT EXISTS' => 'NOT EXISTS (key absent)',
    ],
] );

$meta_repeater->add_control( 'meta_value', [
    'label'       => esc_html__( 'Meta Value', 'softech-addons-for-elementor' ),
    'type'        => Controls_Manager::TEXT,
    'placeholder' => 'e.g.  yes   or   10,20   or   5,15',
    'description' => esc_html__( 'For IN/NOT IN use comma-separated values. For BETWEEN use two comma-separated numbers. Leave empty for EXISTS/NOT EXISTS.', 'softech-addons-for-elementor' ),
    'label_block' => true,
    'condition'   => [ 'meta_compare!' => [ 'EXISTS', 'NOT EXISTS' ] ],
] );

$meta_repeater->add_control( 'meta_type', [
    'label'   => esc_html__( 'Value Type', 'softech-addons-for-elementor' ),
     'type' => Controls_Manager::HIDDEN,
    'default' => 'CHAR',

] );

$widget->add_control( 'meta_query_rows', [
    'label'       => esc_html__( 'Meta Conditions', 'softech-addons-for-elementor' ),
    'type'        => Controls_Manager::REPEATER,
    'fields'      => $meta_repeater->get_controls(),
    'title_field' => '{{{ meta_key }}} {{{ meta_compare }}} {{{ meta_value }}}',
    'condition'   => array_merge( $post_type_condition, [ 'selection' => 'dynamic' ] ),
] );
$widget->add_control(
    'enable_url_filters',
    [
        'label'        => esc_html__( 'Enable URL Query Filters', 'softech-addons-for-elementor' ),
        'type'         => Controls_Manager::SWITCHER,
        'label_on'     => esc_html__( 'Yes', 'softech-addons-for-elementor' ),
        'label_off'    => esc_html__( 'No', 'softech-addons-for-elementor' ),
        'return_value' => 'yes',
        'default'      => '',
        'separator'    => 'before',
    ]
);
		
		// ── Taxonomy URL Filter Rows ──────────────────────────────────────────
$tax_url_repeater = new \Elementor\Repeater();

$tax_url_repeater->add_control( 'tax_url_param', [
    'label'       => esc_html__( 'URL Parameter Key', 'softech-addons-for-elementor' ),
    'type'        => Controls_Manager::TEXT,
    'placeholder' => 'partner',
    'description' => esc_html__( 'The URL query key. Must match the taxonomy slug exactly. e.g. ?partner=nike', 'softech-addons-for-elementor' ),
    'label_block' => true,
] );

$tax_url_repeater->add_control( 'tax_url_taxonomy', [
    'label'       => esc_html__( 'Taxonomy Slug', 'softech-addons-for-elementor' ),
    'type'        => Controls_Manager::TEXT,
    'placeholder' => 'partner',
    'description' => esc_html__( 'The registered taxonomy slug to filter against. Leave empty to use the URL key as the taxonomy slug.', 'softech-addons-for-elementor' ),
    'label_block' => true,
] );

$tax_url_repeater->add_control( 'tax_url_operator', [
    'label'   => esc_html__( 'Operator', 'softech-addons-for-elementor' ),
    'type'    => Controls_Manager::SELECT,
    'default' => 'IN',
    'options' => [
        'IN'     => esc_html__( 'IN (match any)', 'softech-addons-for-elementor' ),
        'NOT IN' => esc_html__( 'NOT IN (exclude)', 'softech-addons-for-elementor' ),
        'AND'    => esc_html__( 'AND (match all)', 'softech-addons-for-elementor' ),
    ],
] );

$widget->add_control( 'tax_url_filter_rows', [
    'label'       => esc_html__( 'Taxonomy URL Filters', 'softech-addons-for-elementor' ),
    'type'        => Controls_Manager::REPEATER,
    'fields'      => $tax_url_repeater->get_controls(),
    'title_field' => '?{{{ tax_url_param }}}= → {{{ tax_url_taxonomy || tax_url_param }}}',
    'condition'   => [ 'enable_url_filters' => 'yes' ],
] );

// ── Static Taxonomy Filter ────────────────────────────────────────────
$widget->add_control( 'static_tax_filter_heading', [
    'label'     => esc_html__( 'Static Taxonomy Filter', 'softech-addons-for-elementor' ),
    'type'      => Controls_Manager::HEADING,
    'separator' => 'before',
    'condition' => array_merge( $post_type_condition, [ 'selection' => 'dynamic' ] ),
] );

$widget->add_control( 'static_tax_relation', [
    'label'     => esc_html__( 'Relation Between Filters', 'softech-addons-for-elementor' ),
    'type'      => Controls_Manager::SELECT,
    'default'   => 'AND',
    'options'   => [
        'AND' => esc_html__( 'AND (must match all)', 'softech-addons-for-elementor' ),
        'OR'  => esc_html__( 'OR (match any)', 'softech-addons-for-elementor' ),
    ],
    'condition' => array_merge( $post_type_condition, [ 'selection' => 'dynamic' ] ),
] );

// Static Taxonomy Filter Repeater
$static_tax_repeater = new \Elementor\Repeater();

$static_tax_repeater->add_control( 'static_taxonomy', [
    'label'       => esc_html__( 'Taxonomy', 'softech-addons-for-elementor' ),
    'type'        => Controls_Manager::SELECT,
    'options'     => self::get_taxonomies_for_select(),
    'default'     => 'category',
    'label_block' => true,
] );

$static_tax_repeater->add_control( 'static_terms', [
    'label'       => esc_html__( 'Terms (slugs or IDs)', 'softech-addons-for-elementor' ),
    'type'        => Controls_Manager::TEXT,
    'placeholder' => 'himapan, olee   or   12, 45',
    'description' => esc_html__( 'Comma-separated term slugs or IDs.', 'softech-addons-for-elementor' ),
    'label_block' => true,
] );

$static_tax_repeater->add_control( 'static_operator', [
    'label'   => esc_html__( 'Operator', 'softech-addons-for-elementor' ),
    'type'    => Controls_Manager::SELECT,
    'default' => 'IN',
    'options' => [
        'IN'     => esc_html__( 'IN (include)', 'softech-addons-for-elementor' ),
        'NOT IN' => esc_html__( 'NOT IN (exclude)', 'softech-addons-for-elementor' ),
        'AND'    => esc_html__( 'AND (match all terms)', 'softech-addons-for-elementor' ),
    ],
] );

$widget->add_control( 'static_tax_filter_rows', [
    'label'       => esc_html__( 'Taxonomy Filters', 'softech-addons-for-elementor' ),
    'type'        => Controls_Manager::REPEATER,
    'fields'      => $static_tax_repeater->get_controls(),
    'title_field' => '{{{ static_taxonomy }}} → {{{ static_terms }}}',
    'condition'   => array_merge( $post_type_condition, [ 'selection' => 'dynamic' ] ),
] );

        // ── Pagination ────────────────────────────────────────────────────
        $widget->add_control( 'posts_per_page', [
            'label'     => esc_html__( 'Items Per Page', 'softech-addons-for-elementor' ),
            'type'      => Controls_Manager::NUMBER,
            'default'   => 6,
            'min'       => -1,
            'max'       => 100,
            'description' => esc_html__( 'Use -1 to show all items.', 'softech-addons-for-elementor' ),
            'separator' => 'before',
        ] );

        $widget->add_control( 'not_found_text', [
            'label'   => esc_html__( 'No Items Found Text', 'softech-addons-for-elementor' ),
            'type'    => Controls_Manager::TEXT,
            'default' => esc_html__( 'No items found.', 'softech-addons-for-elementor' ),
        ] );
        

        $widget->end_controls_section();
    }

    // ─────────────────────────────────────────────────────────────────────
    // 2. QUERY ARGS BUILDER
    // ─────────────────────────────────────────────────────────────────────

    /**
     * Build and return WP_Query args from widget $settings.
     *
     * @param array $settings The widget's get_settings_for_display() array.
     * @param int   $paged    Current page number (for pagination).
     * @return array          Ready-to-pass args for new \WP_Query( $args ).
     */
    public static function build_query_args( array $settings, int $paged = 1 ): array {

        $per_page = self::normalize_posts_per_page_value( $settings['posts_per_page'] ?? 6 );

        // ── Detect mode by which settings key is present ──────────────────
        if ( isset( $settings['choose_template_type'] ) ) {
            // full mode
            $template_type  = $settings['choose_template_type'];
            $all_post_types = array_keys( self::get_post_types() );

            if ( ! in_array( $template_type, $all_post_types, true ) ) {
                // _taxonomy type — return a marker for the widget to handle.
                return [
                    '_query_type' => 'taxonomy',
                    'taxonomy'    => sanitize_key( $settings['source_post_taxonomy'] ?? 'category' ),
                    'per_page'    => $per_page,
                    'paged'       => $paged,
                ];
            }

            $post_type = sanitize_key( $template_type );

        } else {
            // post_only mode
            $post_type = sanitize_key( $settings['post_type'] ?? 'post' );
        }

        // ── Base args ────────────────────────────────────────────────────
        $args = [
            'post_type'      => $post_type,
            'post_status'    => 'publish',
            'posts_per_page' => $per_page,
            'paged'          => $paged,
            'no_found_rows'  => ( 'yes' !== ( $settings['show_pagination'] ?? '' ) ),
        ];

        // ── Manual vs Dynamic ─────────────────────────────────────────────
        if ( 'manual' === ( $settings['selection'] ?? 'dynamic' ) && ! empty( $settings['manual_posts'] ) ) {

            $args['post__in'] = array_map( 'absint', (array) $settings['manual_posts'] );
            $args['orderby']  = 'post__in';

        } else {

            // Ordering
            $args['orderby'] = sanitize_key( $settings['orderby'] ?? 'date' );
            $args['order']   = in_array( $settings['order'] ?? 'DESC', [ 'ASC', 'DESC' ], true )
                               ? $settings['order'] : 'DESC';

            // Offset
            if ( ! empty( $settings['offset'] ) && 1 === $paged ) {
                $args['offset'] = absint( $settings['offset'] );
            }

            // Include categories
            if ( ! empty( $settings['categories'] ) ) {
                $args['category_name'] = implode( ',', array_map( 'sanitize_key', (array) $settings['categories'] ) );
            }

            // Exclude categories — resolve slugs → term IDs for category__not_in
            if ( ! empty( $settings['exclude_categories'] ) ) {
                $excluded_ids = array_filter( array_map(
                    static function ( $slug ) {
                        $cat = get_category_by_slug( $slug );
                        return $cat ? (int) $cat->term_id : 0;
                    },
                    (array) $settings['exclude_categories']
                ) );

                if ( ! empty( $excluded_ids ) ) {
                    $args['category__not_in'] = array_values( $excluded_ids );
                }
            }

            // Tags
          // Tags include
if ( ! empty( $settings['manual_tags'] ) ) {
    $tags = array_map( 'sanitize_key', (array) $settings['manual_tags'] );

    if ( $post_type === 'product' ) {
        $args['tax_query'][] = [
            'taxonomy' => 'product_tag',
            'field'    => 'slug',
            'terms'    => $tags,
            'operator' => 'IN',
        ];
    } else {
        $args['tag_slug__in'] = $tags;
    }
}

// Tags exclude
if ( ! empty( $settings['exclude_tags'] ) ) {
    $exclude_tags = array_map( 'sanitize_key', (array) $settings['exclude_tags'] );

    if ( $post_type === 'product' ) {
        $args['tax_query'][] = [
            'taxonomy' => 'product_tag',
            'field'    => 'slug',
            'terms'    => $exclude_tags,
            'operator' => 'NOT IN',
        ];
    } else {
        $args['tag__not_in'] = array_map(
            function( $slug ) {
                $term = get_term_by( 'slug', $slug, 'post_tag' );
                return $term ? (int) $term->term_id : 0;
            },
            $exclude_tags
        );
    }
}


            // Meta Query — only applied when a meta_key has been entered
        
$meta_rows = $settings['meta_query_rows'] ?? [];

if ( ! empty( $meta_rows ) && is_array( $meta_rows ) ) {

    $meta_clauses = [];
    $needs_top_level_meta_key = false; // tracks if orderby needs meta_key

    foreach ( $meta_rows as $row ) {
        $key     = trim( $row['meta_key'] ?? '' );
        $compare = $row['meta_compare']  ?? '=';
        $type    = $row['meta_type']     ?? 'CHAR';
        $value   = trim( $row['meta_value'] ?? '' );

        if ( $key === '' ) {
            continue; // skip empty rows
        }

        $clause = [
            'key'     => $key,       // not sanitize_key() — field names allow dashes/dots
            'compare' => $compare,
            'type'    => $type,
        ];

        // EXISTS / NOT EXISTS never get a value
        if ( ! in_array( $compare, [ 'EXISTS', 'NOT EXISTS' ], true ) ) {

            // IN / NOT IN / BETWEEN / NOT BETWEEN need array values
            if ( in_array( $compare, [ 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ], true ) ) {
                $clause['value'] = array_map(
                    'sanitize_text_field',
                    array_map( 'trim', explode( ',', $value ) )
                );
            } else {
                $clause['value'] = sanitize_text_field( $value );
            }
        }

        $meta_clauses[] = $clause;

        // Remember last key in case orderby = meta_value
        $needs_top_level_meta_key = $key;
    }

    if ( ! empty( $meta_clauses ) ) {
        $relation = in_array( $settings['meta_query_relation'] ?? 'AND', [ 'AND', 'OR' ], true )
                    ? $settings['meta_query_relation']
                    : 'AND';

        // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
        $args['meta_query'] = array_merge(
            [ 'relation' => $relation ],
            $meta_clauses
        );

        // If ordering by meta_value, pass the first meta_key at top level
        if ( in_array( $args['orderby'] ?? '', [ 'meta_value', 'meta_value_num' ], true ) ) {
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
            $args['meta_key'] = $meta_clauses[0]['key'];
        }
    }
}
}


// URL Query Parameter Meta Filtering
// Applies only when matching query param exist

if ( ! empty( $_GET ) ) {

    $url_meta_query = [];

    // Collect allowed meta keys from widget meta repeater
    $allowed_meta_keys = [];

    if ( ! empty( $settings['meta_query_rows'] ) ) {

        foreach ( $settings['meta_query_rows'] as $row ) {

            if ( ! empty( $row['meta_key'] ) ) {
                $allowed_meta_keys[] = $row['meta_key'];
            }
        }
    }

    // Remove duplicates
    $allowed_meta_keys = array_unique( $allowed_meta_keys );

    foreach ( $_GET as $param_key => $param_value ) {

        // Skip pagination params
        if ( in_array( $param_key, [ 'paged', 'page' ], true ) ) {
            continue;
        }

        // IMPORTANT:
        // Only apply if this widget actually uses this meta key
        if ( ! in_array( $param_key, $allowed_meta_keys, true ) ) {
            continue;
        }

        // Skip empty values
        if ( '' === $param_value ) {
            continue;
        }

        // Array values
        if ( is_array( $param_value ) ) {

            $clean_values = array_map(
                'sanitize_text_field',
                array_map( 'trim', $param_value )
            );

            $url_meta_query[] = [
                'key'     => $param_key,
                'value'   => $clean_values,
                'compare' => 'IN',
            ];

        } else {

            $url_meta_query[] = [
                'key'     => $param_key,
                'value'   => sanitize_text_field( $param_value ),
                'compare' => '=',
            ];
        }
    }

    // Merge only if matching params found
    if ( ! empty( $url_meta_query ) ) {

        if ( ! empty( $args['meta_query'] ) ) {

            $existing_relation = $args['meta_query']['relation'] ?? 'AND';

            unset( $args['meta_query']['relation'] );

            $args['meta_query'] = array_merge(
                [ 'relation' => $existing_relation ],
                $args['meta_query'],
                $url_meta_query
            );

        } else {

            $args['meta_query'] = array_merge(
                [ 'relation' => 'AND' ],
                $url_meta_query
            );
        }
    }
}
		// ─────────────────────────────────────────────────────────────────────
// URL Query Parameter → Taxonomy Filtering

if (
    ! empty( $settings['enable_url_filters'] )        &&
    'yes' === $settings['enable_url_filters']          &&
    ! empty( $settings['tax_url_filter_rows'] )        &&
    ! empty( $_GET )
) {
    $tax_clauses = [];

    foreach ( $settings['tax_url_filter_rows'] as $row ) {

        // ── Param key ─────────────────────────────────────────────────
        $param_key = sanitize_key( trim( $row['tax_url_param'] ?? '' ) );

        if ( $param_key === '' ) {
            continue;
        }

        // ── Taxonomy slug (falls back to param key if left empty) ─────
        $taxonomy = sanitize_key( trim( $row['tax_url_taxonomy'] ?? '' ) );

        if ( $taxonomy === '' ) {
            $taxonomy = $param_key;
        }

        // ── Verify the taxonomy is registered (safety check) ──────────
        if ( ! taxonomy_exists( $taxonomy ) ) {
            continue;
        }

        // ── Read raw URL value ────────────────────────────────────────
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $raw = $_GET[ $param_key ] ?? null;

        if ( $raw === null || $raw === '' ) {
            continue; // param absent from URL — skip
        }

        // ── Normalise to an array of raw strings ──────────────────────
        if ( is_array( $raw ) ) {
            $raw_values = array_map( 'sanitize_text_field', array_map( 'trim', $raw ) );
        } else {
            $raw_values = array_map( 'sanitize_text_field', array_map( 'trim', explode( ',', $raw ) ) );
        }

        $raw_values = array_filter( $raw_values ); // drop empty strings

        if ( empty( $raw_values ) ) {
            continue;
        }

        // ── Resolve each value: slug → ID, fallback to numeric ID ─────
        $term_ids = [];

        foreach ( $raw_values as $val ) {
            $resolved = self::resolve_term_id( $taxonomy, $val );

            if ( $resolved !== null ) {
                $term_ids[] = $resolved;
            }
        }

        // If none resolved, skip — avoids an empty tax clause
        // which would otherwise match nothing or throw a WP notice.
        if ( empty( $term_ids ) ) {
            continue;
        }

        // ── Operator ──────────────────────────────────────────────────
        $operator = $row['tax_url_operator'] ?? 'IN';

        if ( ! in_array( $operator, [ 'IN', 'NOT IN', 'AND' ], true ) ) {
            $operator = 'IN';
        }

        $tax_clauses[] = [
            'taxonomy' => $taxonomy,
            'field'    => 'term_id',   // always IDs after resolution
            'terms'    => $term_ids,
            'operator' => $operator,
        ];
    }

    // ── Merge into args['tax_query'] ──────────────────────────────────
    if ( ! empty( $tax_clauses ) ) {

        // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
        if ( ! empty( $args['tax_query'] ) ) {

            $existing_relation = $args['tax_query']['relation'] ?? 'AND';
            unset( $args['tax_query']['relation'] );

            $args['tax_query'] = array_merge(
                [ 'relation' => $existing_relation ],
                $args['tax_query'],
                $tax_clauses
            );

        } else {

            $args['tax_query'] = array_merge(
                [ 'relation' => 'AND' ],
                $tax_clauses
            );
        }
    }
}

// ── Static Taxonomy Filter (hardcoded in widget settings) ─────────────
if ( ! empty( $settings['static_tax_filter_rows'] ) && is_array( $settings['static_tax_filter_rows'] ) ) {

    $static_clauses = [];

    foreach ( $settings['static_tax_filter_rows'] as $row ) {

        $taxonomy = sanitize_key( trim( $row['static_taxonomy'] ?? '' ) );
        $raw      = trim( $row['static_terms'] ?? '' );
        $operator = $row['static_operator'] ?? 'IN';

        if ( $taxonomy === '' || $raw === '' ) {
            continue;
        }

        if ( ! taxonomy_exists( $taxonomy ) ) {
            continue;
        }

        if ( ! in_array( $operator, [ 'IN', 'NOT IN', 'AND' ], true ) ) {
            $operator = 'IN';
        }

        // Resolve each value: slug → ID, fallback numeric ID
        $raw_values = array_filter( array_map( 'trim', explode( ',', $raw ) ) );
        $term_ids   = [];

        foreach ( $raw_values as $val ) {
            $resolved = self::resolve_term_id( $taxonomy, $val );
            if ( $resolved !== null ) {
                $term_ids[] = $resolved;
            }
        }

        if ( empty( $term_ids ) ) {
            continue;
        }

        $static_clauses[] = [
            'taxonomy' => $taxonomy,
            'field'    => 'term_id',
            'terms'    => $term_ids,
            'operator' => $operator,
        ];
    }

    if ( ! empty( $static_clauses ) ) {

        $relation = in_array( $settings['static_tax_relation'] ?? 'AND', [ 'AND', 'OR' ], true )
                    ? $settings['static_tax_relation']
                    : 'AND';

        // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
        if ( ! empty( $args['tax_query'] ) ) {
            $existing_relation = $args['tax_query']['relation'] ?? 'AND';
            unset( $args['tax_query']['relation'] );
            $args['tax_query'] = array_merge(
                [ 'relation' => $existing_relation ],
                $args['tax_query'],
                $static_clauses
            );
        } else {
            $args['tax_query'] = array_merge(
                [ 'relation' => $relation ],
                $static_clauses
            );
        }
    }
}
        return apply_filters( 'softech_post_query_args', $args, $settings, $paged );
    }

    // ─────────────────────────────────────────────────────────────────────
    // 3. TAXONOMY QUERY BUILDER
    // ─────────────────────────────────────────────────────────────────────

    /**
     * Build paginated terms array for taxonomy mode.
     *
     * @return array { terms: WP_Term[], total_pages: int, paged: int }
     */
    public static function build_taxonomy_query( array $settings, int $paged = 1 ): array {

        $taxonomy = sanitize_key( $settings['source_post_taxonomy'] ?? 'category' );
        $per_page = self::normalize_posts_per_page_value( $settings['posts_per_page'] ?? 6 );

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

        if ( empty( $all_terms ) || is_wp_error( $all_terms ) ) {
            return [ 'terms' => [], 'total_pages' => 0, 'paged' => $paged ];
        }

        if ( -1 === $per_page ) {
            return [
                'terms'       => $all_terms,
                'total_pages' => 1,
                'paged'       => 1,
            ];
        }

        $total       = count( $all_terms );
        $total_pages = (int) ceil( $total / $per_page );
        $offset      = ( $paged - 1 ) * $per_page;
        $terms       = array_slice( $all_terms, $offset, $per_page );

        return [
            'terms'       => $terms,
            'total_pages' => $total_pages,
            'paged'       => $paged,
        ];
    }

    /**
     * Normalize items-per-page values while preserving -1 as "show all".
     */
    private static function normalize_posts_per_page_value( $value, int $default = 6 ): int {
        if ( is_string( $value ) ) {
            $value = trim( $value );
        }

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

        $value = is_numeric( $value ) ? (int) $value : $default;

        if ( -1 === $value ) {
            return -1;
        }

        return max( 1, $value );
    }

    // ─────────────────────────────────────────────────────────────────────
    // 4. TEMPLATE TYPE HELPERS
    // ─────────────────────────────────────────────────────────────────────

    /** Returns combined options: post types + their _taxonomy variants. */
    public static function get_template_type_options(): array {
        $options = [];
        foreach ( self::get_post_types() as $slug => $label ) {
            $options[ $slug ]               = $label;
            $options[ $slug . '_taxonomy' ] = sprintf(
                /* translators: %s: Post type label */
                esc_html__( '%s Taxonomy', 'softech-addons-for-elementor' ),
                $label
            );
        }
        return $options;
    }

    /** Returns only the plain post type keys e.g. ['post', 'page', 'product']. */
    public static function get_post_type_keys(): array {
        return array_keys( self::get_post_types() );
    }

    /** Returns only the _taxonomy keys e.g. ['post_taxonomy', 'page_taxonomy']. */
    public static function get_taxonomy_type_keys(): array {
        return array_map( fn( $k ) => $k . '_taxonomy', array_keys( self::get_post_types() ) );
    }

    // ─────────────────────────────────────────────────────────────────────
    // 5. PAGED HELPER
    // ─────────────────────────────────────────────────────────────────────

    /**
     * Return the current paged value, supporting both ?paged= and /page/N/ URLs.
     */
    public static function get_paged(): int {
        $paged = (int) get_query_var( 'paged' );
        if ( ! $paged ) {
            $paged = (int) get_query_var( 'page' );
        }
        return max( 1, $paged );
    }

    // ─────────────────────────────────────────────────────────────────────
    // 6. PRIVATE DATA HELPERS (cached lookups for Elementor editor)
    // ─────────────────────────────────────────────────────────────────────

    /** @return array [ slug => label ] of all public non-excluded post types. */
    private static function get_post_types(): array {
        $result = [];
        foreach ( get_post_types( [ 'public' => true ], 'objects' ) as $pt ) {
            if ( ! in_array( $pt->name, self::EXCLUDED_POST_TYPES, true ) ) {
                $result[ $pt->name ] = $pt->label;
            }
        }
        return $result;
    }

    private static function get_taxonomies_for_select(): array {
        $exclude = [ 'post_format', 'elementor_library_category', 'elementor_library_tag' ];
        $result  = [];
        foreach ( get_taxonomies( [ 'public' => true ], 'objects' ) as $tax ) {
            if ( ! in_array( $tax->name, $exclude, true ) ) {
                $result[ $tax->name ] = $tax->label;
            }
        }
        return $result;
    }

    private static function get_posts_for_select(): array {
        if ( ! is_admin() ) {
            return [];
        }

        $key    = 'softech_qm_posts_select';
        $cached = get_transient( $key );
        if ( false !== $cached ) {
            return $cached;
        }

        $result = [];
        $posts  = get_posts( [
            'numberposts' => 200,
            'post_type'   => 'any',
            'post_status' => 'publish',
            'fields'      => 'ids',
        ] );
        foreach ( $posts as $id ) {
            $result[ $id ] = get_the_title( $id );
        }

        set_transient( $key, $result, self::CACHE_EXPIRATION );
        return $result;
    }

/**
 * Get all hierarchical taxonomies (categories) for select2 dropdown.
 *
 * @return array
 */
protected static function get_categories_for_select(): array {
    $options = [];

    // Get only hierarchical taxonomies (categories)
    $taxonomies = get_taxonomies([ 'hierarchical' => true ], 'names');

    foreach ( $taxonomies as $taxonomy ) {
        $terms = get_terms([
            'taxonomy'   => $taxonomy,
            'hide_empty' => false,
        ]);

        if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
            foreach ( $terms as $term ) {
                $options[ $term->slug ] = $term->name;
            }
        }
    }

    return $options;
}




/**
 * Get all tags across supported taxonomies for select2 dropdown.
 *
 * @return array
 */
protected static function get_tags_for_select(): array {
    $options = [];

    // Collect all tag-like taxonomies you want to support
    $tag_taxonomies = [ 'post_tag', 'product_tag' ]; // extend with more if needed

    foreach ( $tag_taxonomies as $taxonomy ) {
        $terms = get_terms([
            'taxonomy'   => $taxonomy,
            'hide_empty' => false,
        ]);

        if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
            foreach ( $terms as $term ) {
                // Use slug as key, plain name as label
                $options[ $term->slug ] = $term->name;
            }
        }
    }

    return $options;
}


	
	/**
 * Resolve a term value to a term ID.
 * Tries slug first, then numeric ID, then returns null if no match.
 *
 * @param string $taxonomy  Registered taxonomy slug.
 * @param string $value     Raw URL param value (single term).
 * @return int|null  Term ID on success, null on failure.
 */
private static function resolve_term_id( string $taxonomy, string $value ): ?int {

    $value = trim( $value );

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

    // ── 1. Try slug first ─────────────────────────────────────────────
    $term = get_term_by( 'slug', $value, $taxonomy );

    if ( $term instanceof \WP_Term ) {
        return (int) $term->term_id;
    }

    // ── 2. Fall back to numeric ID ────────────────────────────────────
    if ( is_numeric( $value ) ) {
        $term = get_term_by( 'id', (int) $value, $taxonomy );

        if ( $term instanceof \WP_Term ) {
            return (int) $term->term_id;
        }
    }

    // ── 3. No match ───────────────────────────────────────────────────
    return null;
}
}
