<?php

namespace UltimatePostKit\Includes\Controls\GroupQuery;

use Elementor\Controls_Manager;
use UltimatePostKit\Includes\Controls\SelectInput\Dynamic_Select;
use UltimatePostKit\Base\Module_Base;

if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly.
}

abstract class Group_Control_Query extends Module_Base {

    protected function register_query_builder_controls() {

        $this->add_control(
            'posts_source',
            [
                'label'   => __('Source', 'ultimate-post-kit'),
                'type'    => Controls_Manager::SELECT,
                'options' => $this->getGroupControlQueryPostTypes(),
                'default' => 'post',
            ]
        );

        $this->add_control(
            'posts_source_description',
            [
                'label'     => '',
                'type'      => Controls_Manager::RAW_HTML,
                'raw'       => __('This current query may not be display posts in the editor. Please save and refresh the page to see the posts.', 'ultimate-post-kit'),
                'content_classes' => 'elementor-panel-alert elementor-panel-alert-info',
                'condition' => [
                    'posts_source' => 'current_query',
                ],
            ]
        );

        $this->add_control(
            'posts_source_description_2',
            [
                'label'     => '',
                'type'      => Controls_Manager::RAW_HTML,
                'raw'       => sprintf(
                    /* translators: 1: HTML link to options-reading.php */
                    __('Posts per page is global for current query. Set <strong>"Blog pages show at most"</strong> from settings page of WordPress. Go to <a href="%s" target="_blank" rel="noopener">Settings &gt; Reading</a>.', 'ultimate-post-kit'),
                    esc_url(admin_url('options-reading.php'))
                ),
                'content_classes' => 'elementor-panel-alert elementor-panel-alert-info',
                'condition' => [
                    'posts_source' => 'current_query',
                ],
            ]
        );

        $this->add_control(
            'posts_selected_ids',
            [
                'label'       => __('Search & Select', 'ultimate-post-kit'),
                'type'        => Dynamic_Select::TYPE,
                'multiple'    => true,
                'label_block' => true,
                'query_args'  => [
                    'query' => 'posts',
                ],
                'condition'   => [
                    'posts_source' => 'manual_selection',
                ]
            ]
        );

        $this->start_controls_tabs(
            'tabs_posts_include_exclude',
            [
                'condition' => [
                    'posts_source!' => ['manual_selection', 'current_query'],
                ]
            ]
        );

        $this->start_controls_tab(
            'tab_posts_include',
            [
                'label'     => __('Include', 'ultimate-post-kit'),
                'condition' => [
                    'posts_source!' => ['manual_selection', 'current_query'],
                ]
            ]
        );

        $this->add_control(
            'posts_include_by',
            [
                'label'       => __('Include By', 'ultimate-post-kit'),
                'type'        => Controls_Manager::SELECT2,
                'multiple'    => true,
                'label_block' => true,
                'options'     => [
                    'authors' => __('Authors', 'ultimate-post-kit'),
                    'terms'   => __('Terms', 'ultimate-post-kit'),
                ],
                'condition'   => [
                    'posts_source!' => ['manual_selection', 'current_query'],
                ]
            ]
        );

        $this->add_control(
            'posts_include_author_ids',
            [
                'label'       => __('Authors', 'ultimate-post-kit'),
                'type'        => Dynamic_Select::TYPE,
                'multiple'    => true,
                'label_block' => true,
                'query_args'  => [
                    'query' => 'authors',
                ],
                'condition'   => [
                    'posts_include_by' => 'authors',
                    'posts_source!'    => ['manual_selection', 'current_query'],
                ]
            ]
        );

        $this->add_control(
            'posts_include_term_ids',
            [
                'label'       => __('Terms', 'ultimate-post-kit'),
                'description' => __('Terms are items in a taxonomy. The available taxonomies are: Categories, Tags, Formats and custom taxonomies.', 'ultimate-post-kit'),
                'type'        => Dynamic_Select::TYPE,
                'multiple'    => true,
                'label_block' => true,
                'placeholder' => __('Type and select terms', 'ultimate-post-kit'),
                'query_args'  => [
                    'query'        => 'terms',
                    'widget_props' => [
                        'post_type' => 'posts_source'
                    ]
                ],
                'condition'   => [
                    'posts_include_by' => 'terms',
                    'posts_source!'    => ['manual_selection', 'current_query'],
                ]
            ]
        );

        $this->end_controls_tab();

        $this->start_controls_tab(
            'tab_posts_exclude',
            [
                'label'     => __('Exclude', 'ultimate-post-kit'),
                'condition' => [
                    'posts_source!' => ['manual_selection', 'current_query'],
                ]
            ]
        );

        $this->add_control(
            'posts_exclude_by',
            [
                'label'       => __('Exclude By', 'ultimate-post-kit'),
                'type'        => Controls_Manager::SELECT2,
                'multiple'    => true,
                'label_block' => true,
                'options'     => [
                    'authors'          => __('Authors', 'ultimate-post-kit'),
                    'current_post'     => __('Current Post', 'ultimate-post-kit'),
                    'manual_selection' => __('Manual Selection', 'ultimate-post-kit'),
                    'terms'            => __('Terms', 'ultimate-post-kit'),
                ],
                'condition'   => [
                    'posts_source!' => ['manual_selection', 'current_query'],
                ]
            ]
        );

        $this->add_control(
            'posts_exclude_ids',
            [
                'label'       => __('Search & Select', 'ultimate-post-kit'),
                'type'        => Dynamic_Select::TYPE,
                'multiple'    => true,
                'label_block' => true,
                'query_args'  => [
                    'query'        => 'posts',
                    'widget_props' => [
                        'post_type' => 'posts_source'
                    ]
                ],
                'condition'   => [
                    'posts_source!'    => ['manual_selection', 'current_query'],
                    'posts_exclude_by' => 'manual_selection',
                ]
            ]
        );

        $this->add_control(
            'posts_exclude_author_ids',
            [
                'label'       => __('Authors', 'ultimate-post-kit'),
                'type'        => Dynamic_Select::TYPE,
                'multiple'    => true,
                'label_block' => true,
                'query_args'  => [
                    'query' => 'authors',
                ],
                'condition'   => [
                    'posts_exclude_by' => 'authors',
                    'posts_source!'    => ['manual_selection', 'current_query'],
                ]
            ]
        );

        $this->add_control(
            'posts_exclude_term_ids',
            [
                'label'       => __('Terms', 'ultimate-post-kit'),
                'description' => __('Terms are items in a taxonomy. The available taxonomies are: Categories, Tags, Formats and custom taxonomies.', 'ultimate-post-kit'),
                'type'        => Dynamic_Select::TYPE,
                'multiple'    => true,
                'label_block' => true,
                'placeholder' => __('Type and select terms', 'ultimate-post-kit'),
                'query_args'  => [
                    'query'        => 'terms',
                    'widget_props' => [
                        'post_type' => 'posts_source'
                    ]
                ],
                'condition'   => [
                    'posts_exclude_by' => 'terms',
                    'posts_source!'    => ['manual_selection', 'current_query'],
                ]
            ]
        );

        $this->end_controls_tab();
        $this->end_controls_tabs();

        $this->add_control(
            'posts_divider',
            [
                'type' => Controls_Manager::DIVIDER,
                'condition' => [
                    'posts_source!' => 'current_query',
                ]
            ]
        );

        $this->add_control(
            'posts_offset',
            [
                'label'   => __('Offset', 'ultimate-post-kit'),
                'type'    => Controls_Manager::NUMBER,
                'default' => 0,
            ]
        );

        $this->add_control(
            'posts_select_date',
            [
                'label'     => __('Date', 'ultimate-post-kit'),
                'type'      => Controls_Manager::SELECT,
                'default'   => 'anytime',
                'options'   => [
                    'anytime' => __('All', 'ultimate-post-kit'),
                    'today'   => __('Past Day', 'ultimate-post-kit'),
                    'week'    => __('Past Week', 'ultimate-post-kit'),
                    'month'   => __('Past Month', 'ultimate-post-kit'),
                    'quarter' => __('Past Quarter', 'ultimate-post-kit'),
                    'year'    => __('Past Year', 'ultimate-post-kit'),
                    'exact'   => __('Custom', 'ultimate-post-kit'),
                ],
                'condition' => [
                    'posts_source!' => 'current_query',
                ]
            ]
        );

        $this->add_control(
            'posts_date_before',
            [
                'label'       => __('Before', 'ultimate-post-kit'),
                'type'        => Controls_Manager::DATE_TIME,
                'description' => __('Setting a ‘Before’ date will show all the posts published until the chosen date (inclusive).', 'ultimate-post-kit'),
                'condition'   => [
                    'posts_select_date' => 'exact',
                    'posts_source!'     => 'current_query',
                ]
            ]
        );

        $this->add_control(
            'posts_date_after',
            [
                'label'       => __('After', 'ultimate-post-kit'),
                'type'        => Controls_Manager::DATE_TIME,
                'description' => __('Setting an ‘After’ date will show all the posts published since the chosen date (inclusive).', 'ultimate-post-kit'),
                'condition'   => [
                    'posts_select_date' => 'exact',
                    'posts_source!'     => 'current_query',
                ]
            ]
        );

        $this->add_control(
            'posts_orderby',
            [
                'label'     => __('Order By', 'ultimate-post-kit'),
                'type'      => Controls_Manager::SELECT,
                'default'   => 'date',
                'options'   => [
                    'title'         => __('Title', 'ultimate-post-kit'),
                    'ID'            => __('ID', 'ultimate-post-kit'),
                    'date'          => __('Date', 'ultimate-post-kit'),
                    'author'        => __('Author', 'ultimate-post-kit'),
                    'comment_count' => __('Comment Count', 'ultimate-post-kit'),
                    'menu_order'    => __('Menu Order', 'ultimate-post-kit'),
					'modified'      => __( 'Last Modified', 'ultimate-post-kit' ),
                    'rand'          => __('Random', 'ultimate-post-kit'),
                ],
            ]
        );

        $this->add_control(
            'posts_order',
            [
                'label'     => __('Order', 'ultimate-post-kit'),
                'type'      => Controls_Manager::SELECT,
                'default'   => 'desc',
                'options'   => [
                    'asc'  => __('ASC', 'ultimate-post-kit'),
                    'desc' => __('DESC', 'ultimate-post-kit'),
                ],
            ]
        );

        $this->add_control(
            'posts_ignore_sticky_posts',
            [
                'label'        => __('Ignore Sticky Posts', 'ultimate-post-kit'),
                'type'         => Controls_Manager::SWITCHER,
                'return_value' => 'yes',
                'default'      => 'yes',
                'condition'    => [
                    'posts_source' => ['post', 'current_query'],
                ]
            ]
        );

        $this->add_control(
            'posts_only_with_featured_image',
            [
                'label'        => __('Only Featured Image Post', 'ultimate-post-kit'),
                'description'  => __('Enable to display posts only when featured image is present.', 'ultimate-post-kit'),
                'type'         => Controls_Manager::SWITCHER,
                'return_value' => 'yes',
                'condition'    => [
                    'posts_source!' => 'current_query',
                ]
            ]
        );

        $this->add_control(
            'query_id',
            [
                'label'       => __('Query ID', 'ultimate-post-kit'),
                'description' => __('Give your Query a custom unique id to allow server side filtering', 'ultimate-post-kit'),
                'type'        => Controls_Manager::TEXT,
                'separator'   => 'before',
            ]
        );
    }

    private function setMetaQueryArgs() {

        $args = [];

        if ('current_query' === $this->getGroupControlQueryPostType()) {
            return [];
        }

        $args['order']   = $this->get_settings_for_display('posts_order');
        $args['orderby'] = $this->get_settings_for_display('posts_orderby');

        /**
         * Set Feature Images
         */

        if ($this->get_settings_for_display('posts_only_with_featured_image') === 'yes') {
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Elementor widget query built from user-configured controls; expected behaviour.
            $args['meta_key'] = '_thumbnail_id';
        }

        /**
         * Set Date
         */

        $selected_date = $this->get_settings_for_display('posts_select_date');

        if (!empty($selected_date)) {
            $date_query = [];

            switch ($selected_date) {
                case 'today':
                    $date_query['after'] = '-1 day';
                    break;

                case 'week':
                    $date_query['after'] = '-1 week';
                    break;

                case 'month':
                    $date_query['after'] = '-1 month';
                    break;

                case 'quarter':
                    $date_query['after'] = '-3 month';
                    break;

                case 'year':
                    $date_query['after'] = '-1 year';
                    break;

                case 'exact':
                    $after_date = $this->get_settings_for_display('posts_date_after');

                    if (!empty($after_date)) {
                        $date_query['after'] = $after_date;
                    }

                    $before_date = $this->get_settings_for_display('posts_date_before');

                    if (!empty($before_date)) {
                        $date_query['before'] = $before_date;
                    }

                    $date_query['inclusive'] = true;
                    break;
            }

            if (!empty($date_query)) {
                $args['date_query'] = $date_query;
            }
        }

        return $args;
    }

    protected function getGroupControlQueryArgs() {

        $settings = $this->get_settings_for_display();
        $args     = $this->setMetaQueryArgs();

        $args['post_status']      = 'publish';
        $args['suppress_filters'] = false;
        $exclude_by               = $this->getGroupControlQueryParamBy('exclude');

        if (0 < $settings['posts_offset']) {
            $args['offset_to_fix'] = $settings['posts_offset'];
        }

        /**
         * Set Ignore Sticky
         */
        if (
            $this->getGroupControlQueryPostType() === 'post'
            && $this->get_settings_for_display('posts_ignore_sticky_posts') === 'yes'
        ) {
            $args['ignore_sticky_posts'] = true;

            if (in_array('current_post', $exclude_by)) {
                // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Elementor widget query built from user-configured controls; expected behaviour.
                $args['post__not_in'] = [get_the_ID()];
            }
        }

        if ($this->getGroupControlQueryPostType() === 'manual_selection') {
            /**
             * Set Including Manually
             */
            $selected_ids      = $this->get_settings_for_display('posts_selected_ids');
            $selected_ids      = wp_parse_id_list($selected_ids);
            $args['post_type'] = 'any';
            if (!empty($selected_ids)) {
                $args['post__in'] = $selected_ids;
            }

            $args['ignore_sticky_posts'] = 1;
        } elseif ('current_query' === $this->getGroupControlQueryPostType()) {
            /**
             * Make Current Query
             */
            $args = $GLOBALS['wp_query']->query_vars;
            $args = apply_filters('ultimate_post_kit/query/get_query_args/current_query', $args);
        } elseif ('_ultimate_post_kit_pro_related_post_type' == $this->getGroupControlQueryPostType()) {
            /**
             * Set Related Query
             */
            $post_id           = get_queried_object_id();
            $related_post_id   = is_singular() && (0 !== $post_id) ? $post_id : null;
            $args['post_type'] = get_post_type($related_post_id);

            $exclude_by = $this->getGroupControlQueryParamBy('exclude');
            if (in_array('current_post', $exclude_by)) {
                // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Elementor widget query built from user-configured controls; expected behaviour.
                $args['post__not_in'] = [get_the_ID()];
            }

            /**
             * Set Authors
             */
            $args = $this->getAuthorArgs($args, $settings, $related_post_id);

            /**
             * Set Taxonomy
             */
            $args = $this->getTermsArgs($args, $settings);

            $args['ignore_sticky_posts'] = 1;
            $args                        = apply_filters('ultimate_post_kit/query/get_query_args/related_query', $args);
        } else {

            /**
             * Set Post Type
             */
            $args['post_type'] = $this->getGroupControlQueryPostType();

            /**
             * Set Exclude Post
             */
            $exclude_by   = $this->getGroupControlQueryParamBy('exclude');
            $current_post = [];

            if (in_array('current_post', $exclude_by) && is_singular()) {
                $current_post = [get_the_ID()];
            }

            if (in_array('manual_selection', $exclude_by)) {
                $exclude_ids          = $settings['posts_exclude_ids'];
                // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Elementor widget query built from user-configured controls; expected behaviour.
                $args['post__not_in'] = array_merge($current_post, wp_parse_id_list($exclude_ids));
            }

            /**
             * Set Authors
             */
            $args = $this->getAuthorArgs($args, $settings);

            /**
             * Set Taxonomy
             */
            $args = $this->getTermsArgs($args, $settings);
        }

        if ($this->get_settings_for_display('query_id')) {
            $args['upk_widget_query'] = true;
            add_action('pre_get_posts', [$this, 'pre_get_posts_query_filter']);
        }

        // fixing custom offset
        ## https://codex.wordpress.org/Making_Custom_Queries_using_Offset_and_Pagination
        // Only register the fixers when this widget actually uses an offset, and
        // scope them to this widget's own query so they never affect other
        // queries (e.g. another post widget rendered later on the same page).
        if (!empty($args['offset_to_fix'])) {
            add_action('pre_get_posts', [$this, 'fix_query'], 1);
            add_filter('found_posts', [$this, 'prefix_adjust_offset_pagination'], 1, 2);
        }

        return $args;
    }

    private function getAuthorArgs($args, $settings, mixed $post = null) {

        $include_by = $this->getGroupControlQueryParamBy('include');
        $exclude_by = $this->getGroupControlQueryParamBy('exclude');
        $include_users = [];
        $exclude_users = [];

        if (in_array('authors', $include_by)) {
            $include_users = wp_parse_id_list($settings['posts_include_author_ids']);
        } elseif ($post) {
            $include_users = get_post_field('post_author', $post);
        }

        if (in_array('authors', $exclude_by)) {
            $exclude_users = wp_parse_id_list($settings['posts_exclude_author_ids']);
            $include_users = array_diff($include_users, $exclude_users);
        }

        if (!empty($include_users)) {
            $args['author__in'] = $include_users;
        }

        if (!empty($exclude_users)) {
            $args['author__not_in'] = $exclude_users;
        }

        return $args;
    }

    private function getTermsArgs($args, $settings) {

        $include_by     = $this->getGroupControlQueryParamBy('include');
        $exclude_by     = $this->getGroupControlQueryParamBy('exclude');
        $include_terms  = [];
        $terms_query    = [];

        if (in_array('terms', $include_by)) {
            $include_terms = wp_parse_id_list($settings['posts_include_term_ids']);
        }

        if (in_array('terms', $exclude_by)) {
            $exclude_terms = wp_parse_id_list($settings['posts_exclude_term_ids']);
            $include_terms = array_diff($include_terms, $exclude_terms);
        }

        if (!empty($include_terms)) {
            $tax_terms_map = $this->mapGroupControlQuery($include_terms);

            foreach ($tax_terms_map as $tax => $terms) {
                $terms_query[] = [
                    'taxonomy' => $tax,
                    'field'    => 'term_id',
                    'terms'    => $terms,
                    'operator' => 'IN',
                ];
            }
        }

        if (!empty($exclude_terms)) {
            $tax_terms_map = $this->mapGroupControlQuery($exclude_terms);

            foreach ($tax_terms_map as $tax => $terms) {
                $terms_query[] = [
                    'taxonomy' => $tax,
                    'field'    => 'term_id',
                    'terms'    => $terms,
                    'operator' => 'NOT IN',
                ];
            }
        }

        if (!empty($terms_query)) {
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Elementor widget query built from user-configured controls; expected behaviour.
            $args['tax_query']             = $terms_query;
            $args['tax_query']['relation'] = 'AND';
        }

        return $args;
    }

    /**
     * @return mixed
     */
    private function getGroupControlQueryPostType() {
        return $this->get_settings_for_display('posts_source');
    }

    /**
     * Get Query Params by args
     *
     * @param string $by
     *
     * @return array|mixed
     */
    private function getGroupControlQueryParamBy($by = 'exclude') {
        $mapBy = [
            // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- Elementor widget query built from user-configured controls; expected behaviour.
            'exclude' => 'posts_exclude_by',
            'include' => 'posts_include_by',
        ];

        $setting = $this->get_settings_for_display($mapBy[$by]);

        return (!empty($setting) ? $setting : []);
    }

    /**
     * @param array $term_ids
     *
     * @return array
     */
    private function mapGroupControlQuery($term_ids = []) {
        $terms = get_terms(
            [
                'term_taxonomy_id' => $term_ids,
                'hide_empty'       => false,
            ]
        );

        $tax_terms_map = [];

        foreach ($terms as $term) {
            $taxonomy                   = $term->taxonomy;
            $tax_terms_map[$taxonomy][] = $term->term_id;
        }

        return $tax_terms_map;
    }


    /**
     * @return array|string[]|\WP_Post_Type[]
     */
    private function getGroupControlQueryPostTypes() {
        $post_types = get_post_types(['public' => true], 'objects');
        $post_types = array_column($post_types, 'label', 'name');

        $ignorePostTypes = [
            'elementor_library'    => '',
            'attachment'           => '',
            'bdt_template_manager' => '',
            'bdt-custom-template'  => '',
        ];

        $post_types = array_diff_key($post_types, $ignorePostTypes);

        $extra_types = [
            'manual_selection'   => __('Manual Selection', 'ultimate-post-kit'),
            'current_query'      => __('Current Query', 'ultimate-post-kit'),
            '_ultimate_post_kit_pro_related_post_type' => __('Related', 'ultimate-post-kit'),
        ];

        $post_types = array_merge($post_types, $extra_types);

        return $post_types;
    }

    /**
     * @param WP_Query $query fix the offset
     */

    function fix_query( &$query ) {

        // Only touch this widget's own query. The flag travels with the query
        // args, so unrelated queries (other widgets, the main loop, etc.) are
        // left untouched even if this callback is still attached.
        if ( ! isset( $query->query_vars['offset_to_fix'] ) ) {
            return;
        }

        // Self-remove so this only applies to a single query and never leaks
        // into subsequent queries on the same request.
        remove_action( 'pre_get_posts', [ $this, 'fix_query' ], 1 );

        $offset = (int) $query->query_vars['offset_to_fix'];

        $posts_per_page = isset( $query->query_vars['posts_per_page'] )
            ? (int) $query->query_vars['posts_per_page']
            : (int) get_option( 'posts_per_page', 10 );

        if ( $query->is_paged ) {
            $page_offset = $offset + ( ( $query->query_vars['paged'] - 1 ) * $posts_per_page );
            $query->set( 'offset', $page_offset );
        } else {
            $query->set( 'offset', $offset );
        }
    }

    public function prefix_adjust_offset_pagination( $found_posts, $query ) {

        if ( ! isset( $query->query_vars['offset_to_fix'] ) ) {
            return $found_posts;
        }

        remove_filter( 'found_posts', [ $this, 'prefix_adjust_offset_pagination' ], 1 );

        $offset_to_fix = intval( $query->query_vars['offset_to_fix'] );

        if ( $offset_to_fix ) {
            $found_posts -= $offset_to_fix;
        }

        return $found_posts;
    }

    public function pre_get_posts_query_filter( $wp_query ) {

        if ( empty( $wp_query->get( 'upk_widget_query' ) ) ) {
            return;
        }

        remove_action( 'pre_get_posts', [ $this, 'pre_get_posts_query_filter' ] );

        $query_id = $this->get_settings_for_display( 'query_id' );

        if ( $query_id ) {
            do_action( "ultimate_post_kit_pro/query/{$query_id}", $wp_query, $this );
        }
    }
}
