<?php

namespace Calio\Widgets;

use Elementor\Controls_Manager;
use Elementor\Group_Control_Border;
use Elementor\Group_Control_Box_Shadow;
use Elementor\Group_Control_Typography;
use WP_Query;

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

/**
 * Renders a post listing widget with lightweight query controls and editorial card skins.
 */
class PostGridWidget extends BaseWidget {
    public function get_name(): string { return 'calio-post-grid'; }
    public function get_title(): string { return esc_html__('Post Grid', 'calio-addon'); }
    public function get_icon(): string { return 'eicon-posts-grid'; }
    public function get_style_depends(): array { return $this->resolve_widget_style_depends(); }
    public function get_script_depends(): array { return $this->resolve_widget_script_depends(); }

    /**
     * Adds post query, layout and style controls for the grid widget.
     */
    protected function register_controls(): void {
        $this->start_controls_section('query', ['label' => esc_html__('Query', 'calio-addon')]);
        $this->add_control('post_type', ['label' => esc_html__('Post Type', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'post', 'options' => $this->get_post_type_options()]);
        $this->add_control('posts_per_page', ['label' => esc_html__('Posts Per Page', 'calio-addon'), 'type' => Controls_Manager::NUMBER, 'default' => 6, 'min' => 1, 'max' => 24]);
        $this->add_control('offset', ['label' => esc_html__('Offset', 'calio-addon'), 'type' => Controls_Manager::NUMBER, 'default' => 0, 'min' => 0]);
        $this->add_control('categories', ['label' => esc_html__('Categories', 'calio-addon'), 'type' => Controls_Manager::SELECT2, 'multiple' => true, 'label_block' => true, 'options' => $this->get_category_options(), 'condition' => ['post_type' => 'post']]);
        $this->add_control('orderby', ['label' => esc_html__('Order By', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'date', 'options' => ['date' => esc_html__('Date', 'calio-addon'), 'title' => esc_html__('Title', 'calio-addon'), 'rand' => esc_html__('Random', 'calio-addon'), 'comment_count' => esc_html__('Comment Count', 'calio-addon')]]);
        $this->add_control('order', ['label' => esc_html__('Order', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'DESC', 'options' => ['DESC' => 'DESC', 'ASC' => 'ASC']]);
        $this->add_control('ignore_sticky', ['label' => esc_html__('Ignore Sticky Posts', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes', 'condition' => ['post_type' => 'post']]);
        $this->add_control('authors', ['label' => esc_html__('Authors', 'calio-addon'), 'type' => Controls_Manager::SELECT2, 'multiple' => true, 'label_block' => true, 'options' => $this->get_author_options()]);
        $this->add_control('tags', ['label' => esc_html__('Tags', 'calio-addon'), 'type' => Controls_Manager::SELECT2, 'multiple' => true, 'label_block' => true, 'options' => $this->get_tag_options(), 'condition' => ['post_type' => 'post']]);
        $this->add_control('taxonomy_relation', ['label' => esc_html__('Taxonomy Logic', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'OR', 'options' => ['OR' => esc_html__('Match Any', 'calio-addon'), 'AND' => esc_html__('Match All', 'calio-addon')], 'condition' => ['post_type' => 'post']]);
        $this->add_control('include_posts', ['label' => esc_html__('Include Post IDs', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'placeholder' => '12, 24, 36']);
        $this->add_control('exclude_posts', ['label' => esc_html__('Exclude Post IDs', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'placeholder' => '5, 18']);
        $this->add_control('exclude_current_post', ['label' => esc_html__('Exclude Current Post', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => '']);
        $this->end_controls_section();

        $this->start_controls_section('layout', ['label' => esc_html__('Layout', 'calio-addon')]);
        $this->add_control('skin', ['label' => esc_html__('Skin', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'cards', 'options' => ['cards' => esc_html__('Cards', 'calio-addon'), 'editorial' => esc_html__('Editorial', 'calio-addon'), 'overlay' => esc_html__('Overlay', 'calio-addon')]]);
        $this->add_control('columns', ['label' => esc_html__('Columns', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => '3', 'options' => ['1' => '1', '2' => '2', '3' => '3', '4' => '4']]);
        $this->add_control('image_ratio', ['label' => esc_html__('Image Ratio', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'landscape', 'options' => ['landscape' => esc_html__('Landscape', 'calio-addon'), 'square' => esc_html__('Square', 'calio-addon'), 'portrait' => esc_html__('Portrait', 'calio-addon'), 'auto' => esc_html__('Auto', 'calio-addon')]]);
        $this->add_control('show_image', ['label' => esc_html__('Show Image', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('show_category', ['label' => esc_html__('Show Category', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes', 'condition' => ['post_type' => 'post']]);
        $this->add_control('show_meta', ['label' => esc_html__('Show Meta', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('show_author', ['label' => esc_html__('Show Author', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes', 'condition' => ['show_meta' => 'yes']]);
        $this->add_control('show_date', ['label' => esc_html__('Show Date', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes', 'condition' => ['show_meta' => 'yes']]);
        $this->add_control('show_comments', ['label' => esc_html__('Show Comments', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => '', 'condition' => ['show_meta' => 'yes', 'post_type' => 'post']]);
        $this->add_control('show_read_time', ['label' => esc_html__('Show Read Time', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => '', 'condition' => ['show_meta' => 'yes', 'show_excerpt' => 'yes']]);
        $this->add_control('feature_first', ['label' => esc_html__('Feature First Post', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('featured_badge_text', ['label' => esc_html__('Featured Badge', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Featured', 'calio-addon'), 'condition' => ['feature_first' => 'yes']]);
        $this->add_control('eyebrow_source', ['label' => esc_html__('Eyebrow Source', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'category', 'options' => ['none' => esc_html__('None', 'calio-addon'), 'category' => esc_html__('Category', 'calio-addon'), 'post_type' => esc_html__('Post Type', 'calio-addon'), 'date' => esc_html__('Date', 'calio-addon')]]);
        $this->add_control('meta_preset', ['label' => esc_html__('Meta Preset', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'inline', 'options' => ['inline' => esc_html__('Inline', 'calio-addon'), 'stacked' => esc_html__('Stacked', 'calio-addon'), 'compact' => esc_html__('Compact', 'calio-addon')]]);
        $this->add_control('card_chrome', ['label' => esc_html__('Card Chrome', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'soft', 'options' => ['flat' => esc_html__('Flat', 'calio-addon'), 'soft' => esc_html__('Soft', 'calio-addon'), 'outlined' => esc_html__('Outlined', 'calio-addon'), 'elevated' => esc_html__('Elevated', 'calio-addon')]]);
        $this->add_control('show_excerpt', ['label' => esc_html__('Show Excerpt', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('excerpt_length', ['label' => esc_html__('Excerpt Length', 'calio-addon'), 'type' => Controls_Manager::NUMBER, 'default' => 18, 'min' => 5, 'max' => 80, 'condition' => ['show_excerpt' => 'yes']]);
        $this->add_control('show_button', ['label' => esc_html__('Show Button', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('button_text', ['label' => esc_html__('Button Text', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Read More', 'calio-addon'), 'condition' => ['show_button' => 'yes']]);
        $this->add_control('show_empty_state', ['label' => esc_html__('Show Empty State', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('empty_message', ['label' => esc_html__('Empty Message', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('No posts found.', 'calio-addon'), 'condition' => ['show_empty_state' => 'yes']]);
        $this->end_controls_section();

        $this->start_controls_section('pagination', ['label' => esc_html__('Pagination', 'calio-addon')]);
        $this->add_control('pagination_mode', ['label' => esc_html__('Pagination Mode', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'none', 'options' => ['none' => esc_html__('None', 'calio-addon'), 'load_more' => esc_html__('Load More', 'calio-addon'), 'numbers' => esc_html__('Numbers', 'calio-addon')]]);
        $this->add_control('initial_items', ['label' => esc_html__('Initially Visible Items', 'calio-addon'), 'type' => Controls_Manager::NUMBER, 'default' => 6, 'min' => 1, 'max' => 24, 'condition' => ['pagination_mode' => 'load_more']]);
        $this->add_control('load_more_step', ['label' => esc_html__('Load More Count', 'calio-addon'), 'type' => Controls_Manager::NUMBER, 'default' => 3, 'min' => 1, 'max' => 24, 'condition' => ['pagination_mode' => 'load_more']]);
        $this->add_control('load_more_text', ['label' => esc_html__('Load More Text', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Load More', 'calio-addon'), 'condition' => ['pagination_mode' => 'load_more']]);
        $this->add_control('all_loaded_text', ['label' => esc_html__('All Loaded Text', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Everything is loaded.', 'calio-addon'), 'condition' => ['pagination_mode' => 'load_more']]);
        $this->add_control('page_size', ['label' => esc_html__('Items Per Page', 'calio-addon'), 'type' => Controls_Manager::NUMBER, 'default' => 6, 'min' => 1, 'max' => 24, 'condition' => ['pagination_mode' => 'numbers']]);
        $this->add_control('prev_text', ['label' => esc_html__('Previous Text', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Previous', 'calio-addon'), 'condition' => ['pagination_mode' => 'numbers']]);
        $this->add_control('next_text', ['label' => esc_html__('Next Text', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Next', 'calio-addon'), 'condition' => ['pagination_mode' => 'numbers']]);
        $this->end_controls_section();

        $this->start_controls_section('style_cards', ['label' => esc_html__('Cards', 'calio-addon'), 'tab' => Controls_Manager::TAB_STYLE]);
        $this->add_control('grid_gap', ['label' => esc_html__('Gap', 'calio-addon'), 'type' => Controls_Manager::SLIDER, 'range' => ['px' => ['min' => 0, 'max' => 40]], 'selectors' => ['{{WRAPPER}} .calio-post-grid' => 'gap: {{SIZE}}{{UNIT}};']]);
        $this->add_style_heading('post_grid_card_spacing_heading', esc_html__('Shape & Spacing', 'calio-addon'));
        $this->add_dimensions_control('card_padding', esc_html__('Content Padding', 'calio-addon'), '{{WRAPPER}} .calio-post-grid-content', 'padding');
        $this->add_dimensions_control('card_margin', esc_html__('Card Margin', 'calio-addon'), '{{WRAPPER}} .calio-post-grid-card', 'margin');
        $this->add_dimensions_control('card_radius', esc_html__('Radius', 'calio-addon'), ['{{WRAPPER}} .calio-post-grid-card', '{{WRAPPER}} .calio-post-grid-media', '{{WRAPPER}} .calio-post-grid-media img'], 'border-radius', ['size_units' => ['px', '%']]);
        $this->add_style_heading('post_grid_card_surface_heading', esc_html__('Surface States', 'calio-addon'));
        $this->start_controls_tabs('post_grid_card_state_tabs');
        $this->start_controls_tab('post_grid_card_state_normal', ['label' => esc_html__('Normal', 'calio-addon')]);
        $this->add_background_type_control('post_grid_card_bg_normal_group', '{{WRAPPER}} .calio-post-grid-card');
        $this->add_border_paint_controls('post_grid_card_border_normal', esc_html__('Border Color', 'calio-addon'), '{{WRAPPER}} .calio-post-grid-card');
        $this->end_controls_tab();
        $this->start_controls_tab('post_grid_card_state_hover', ['label' => esc_html__('Hover', 'calio-addon')]);
        $this->add_background_type_control('post_grid_card_bg_hover_group', '{{WRAPPER}} .calio-post-grid-card:hover');
        $this->add_border_paint_controls('post_grid_card_border_hover', esc_html__('Border Color', 'calio-addon'), '{{WRAPPER}} .calio-post-grid-card:hover');
        $this->end_controls_tab();
        $this->start_controls_tab('post_grid_card_state_active', ['label' => esc_html__('Active', 'calio-addon')]);
        $this->add_background_type_control('post_grid_card_bg_active_group', '{{WRAPPER}} .calio-post-grid-card:focus-within');
        $this->add_border_paint_controls('post_grid_card_border_active', esc_html__('Border Color', 'calio-addon'), '{{WRAPPER}} .calio-post-grid-card:focus-within');
        $this->end_controls_tab();
        $this->end_controls_tabs();
        $this->add_style_heading('post_grid_card_outline_heading', esc_html__('Outline & Shadow', 'calio-addon'));
        $this->add_group_control(Group_Control_Border::get_type(), ['name' => 'card_border', 'selector' => '{{WRAPPER}} .calio-post-grid-card']);
        $this->add_group_control(Group_Control_Box_Shadow::get_type(), ['name' => 'card_shadow', 'selector' => '{{WRAPPER}} .calio-post-grid-card']);
        $this->end_controls_section();

        $this->start_controls_section('style_media', ['label' => esc_html__('Media', 'calio-addon'), 'tab' => Controls_Manager::TAB_STYLE, 'condition' => ['show_image' => 'yes']]);
        $this->add_control('image_height', ['label' => esc_html__('Image Height', 'calio-addon'), 'type' => Controls_Manager::SLIDER, 'range' => ['px' => ['min' => 140, 'max' => 420]], 'selectors' => ['{{WRAPPER}} .calio-post-grid-media' => 'min-height: {{SIZE}}{{UNIT}};']]);
        $this->add_background_paint_controls('post_grid_overlay', esc_html__('Overlay Tint', 'calio-addon'), '{{WRAPPER}} .calio-post-grid.skin-overlay .calio-post-grid-media::after');
        $this->end_controls_section();

        $this->start_controls_section('style_title', ['label' => esc_html__('Title', 'calio-addon'), 'tab' => Controls_Manager::TAB_STYLE]);
        $this->add_group_control(Group_Control_Typography::get_type(), ['name' => 'title_typography', 'selector' => '{{WRAPPER}} .calio-post-grid-title, {{WRAPPER}} .calio-post-grid-title a']);
        $this->start_controls_tabs('post_grid_title_state_tabs');
        $this->start_controls_tab('post_grid_title_state_normal', ['label' => esc_html__('Normal', 'calio-addon')]);
        $this->add_text_paint_controls('post_grid_title_normal', esc_html__('Color', 'calio-addon'), ['{{WRAPPER}} .calio-post-grid-title', '{{WRAPPER}} .calio-post-grid-title a']);
        $this->end_controls_tab();
        $this->start_controls_tab('post_grid_title_state_hover', ['label' => esc_html__('Hover', 'calio-addon')]);
        $this->add_text_paint_controls('post_grid_title_hover', esc_html__('Color', 'calio-addon'), ['{{WRAPPER}} .calio-post-grid-card:hover .calio-post-grid-title', '{{WRAPPER}} .calio-post-grid-card:hover .calio-post-grid-title a']);
        $this->end_controls_tab();
        $this->start_controls_tab('post_grid_title_state_active', ['label' => esc_html__('Active', 'calio-addon')]);
        $this->add_text_paint_controls('post_grid_title_active', esc_html__('Color', 'calio-addon'), ['{{WRAPPER}} .calio-post-grid-card:focus-within .calio-post-grid-title', '{{WRAPPER}} .calio-post-grid-card:focus-within .calio-post-grid-title a']);
        $this->end_controls_tab();
        $this->end_controls_tabs();
        $this->end_controls_section();

        $this->start_controls_section('style_meta', ['label' => esc_html__('Meta', 'calio-addon'), 'tab' => Controls_Manager::TAB_STYLE]);
        $this->add_group_control(Group_Control_Typography::get_type(), ['name' => 'meta_typography', 'selector' => '{{WRAPPER}} .calio-post-grid-meta, {{WRAPPER}} .calio-post-grid-category']);
        $this->add_text_paint_controls('post_grid_category_text', esc_html__('Category Text Color', 'calio-addon'), '{{WRAPPER}} .calio-post-grid-category');
        $this->add_background_type_control('post_grid_category_bg_group', '{{WRAPPER}} .calio-post-grid-category');
        $this->add_text_paint_controls('post_grid_meta_text', esc_html__('Meta Color', 'calio-addon'), '{{WRAPPER}} .calio-post-grid-meta');
        $this->add_text_paint_controls('post_grid_eyebrow_text', esc_html__('Eyebrow Color', 'calio-addon'), '{{WRAPPER}} .calio-post-grid-eyebrow');
        $this->add_text_paint_controls('post_grid_featured_badge_text', esc_html__('Featured Badge Text', 'calio-addon'), '{{WRAPPER}} .calio-post-grid-featured-badge');
        $this->add_background_type_control('post_grid_featured_badge_bg_group', '{{WRAPPER}} .calio-post-grid-featured-badge');
        $this->add_text_paint_controls('post_grid_excerpt_text', esc_html__('Excerpt Color', 'calio-addon'), '{{WRAPPER}} .calio-post-grid-excerpt');
        $this->end_controls_section();

        $this->start_controls_section('style_button', ['label' => esc_html__('Button', 'calio-addon'), 'tab' => Controls_Manager::TAB_STYLE, 'condition' => ['show_button' => 'yes']]);
        $this->add_group_control(Group_Control_Typography::get_type(), ['name' => 'button_typography', 'selector' => '{{WRAPPER}} .calio-post-grid-button']);
        $this->add_style_heading('post_grid_button_surface_heading', esc_html__('Surface States', 'calio-addon'));
        $this->start_controls_tabs('post_grid_button_state_tabs');
        $this->start_controls_tab('post_grid_button_state_normal', ['label' => esc_html__('Normal', 'calio-addon')]);
        $this->add_text_paint_controls('post_grid_button_text_normal', esc_html__('Text Color', 'calio-addon'), '{{WRAPPER}} .calio-post-grid-button');
        $this->add_background_type_control('post_grid_button_bg_normal_group', '{{WRAPPER}} .calio-post-grid-button');
        $this->end_controls_tab();
        $this->start_controls_tab('post_grid_button_state_hover', ['label' => esc_html__('Hover', 'calio-addon')]);
        $this->add_text_paint_controls('post_grid_button_text_hover', esc_html__('Text Color', 'calio-addon'), '{{WRAPPER}} .calio-post-grid-button:hover');
        $this->add_background_type_control('post_grid_button_bg_hover_group', '{{WRAPPER}} .calio-post-grid-button:hover');
        $this->end_controls_tab();
        $this->start_controls_tab('post_grid_button_state_active', ['label' => esc_html__('Active', 'calio-addon')]);
        $this->add_text_paint_controls('post_grid_button_text_active', esc_html__('Text Color', 'calio-addon'), ['{{WRAPPER}} .calio-post-grid-button:focus-visible', '{{WRAPPER}} .calio-post-grid-button:active']);
        $this->add_background_type_control('post_grid_button_bg_active_group', ['{{WRAPPER}} .calio-post-grid-button:focus-visible', '{{WRAPPER}} .calio-post-grid-button:active']);
        $this->end_controls_tab();
        $this->end_controls_tabs();
        $this->add_style_heading('post_grid_button_outline_heading', esc_html__('Border Type & Shadow', 'calio-addon'));
        $this->add_group_control(Group_Control_Border::get_type(), ['name' => 'post_grid_button_border', 'selector' => '{{WRAPPER}} .calio-post-grid-button']);
        $this->add_group_control(Group_Control_Box_Shadow::get_type(), ['name' => 'post_grid_button_shadow', 'selector' => '{{WRAPPER}} .calio-post-grid-button']);
        $this->add_style_heading('post_grid_button_spacing_heading', esc_html__('Shape & Spacing', 'calio-addon'));
        $this->add_dimensions_control('button_padding', esc_html__('Padding', 'calio-addon'), '{{WRAPPER}} .calio-post-grid-button', 'padding');
        $this->add_dimensions_control('button_margin', esc_html__('Margin', 'calio-addon'), '{{WRAPPER}} .calio-post-grid-button', 'margin');
        $this->add_dimensions_control('button_radius', esc_html__('Radius', 'calio-addon'), '{{WRAPPER}} .calio-post-grid-button', 'border-radius', ['size_units' => ['px', '%']]);
        $this->end_controls_section();
    }

    /**
     * Outputs a query-driven post grid with safe fallbacks for empty states.
     */
    protected function render(): void {
        $settings = $this->get_settings_for_display();
        $query = new WP_Query($this->build_query_args($settings));
        $pagination_mode = (string) ($settings['pagination_mode'] ?? 'none');
        $total_posts = (int) $query->post_count;
        $initial_items = max(1, min(24, (int) ($settings['initial_items'] ?? 6)));
        $page_size = max(1, min(24, (int) ($settings['page_size'] ?? 6)));
        $load_more_step = max(1, min(24, (int) ($settings['load_more_step'] ?? 3)));
        $use_load_more = ($pagination_mode === 'load_more') && ($total_posts > $initial_items);
        $use_numbers = ($pagination_mode === 'numbers') && ($total_posts > $page_size);
        $grid_id = 'calio-post-grid-' . $this->get_id();

        if (!$query->have_posts()) {
            if (($settings['show_empty_state'] ?? 'yes') === 'yes') {
                echo '<div class="calio-post-grid-empty">' . esc_html((string) ($settings['empty_message'] ?? esc_html__('No posts found.', 'calio-addon'))) . '</div>';
            }
            wp_reset_postdata();
            return;
        }

        $classes = [
            'calio-post-grid',
            'skin-' . sanitize_html_class((string) ($settings['skin'] ?? 'cards')),
            'cols-' . sanitize_html_class((string) ($settings['columns'] ?? '3')),
            'ratio-' . sanitize_html_class((string) ($settings['image_ratio'] ?? 'landscape')),
            'meta-' . sanitize_html_class((string) ($settings['meta_preset'] ?? 'inline')),
            'chrome-' . sanitize_html_class((string) ($settings['card_chrome'] ?? 'soft')),
        ];
        if (($settings['feature_first'] ?? 'yes') === 'yes') { $classes[] = 'feature-first'; }
        if ($use_load_more) { $classes[] = 'has-load-more'; }
        if ($use_numbers) { $classes[] = 'has-numeric-pagination'; }

        $index = 0;
        echo '<div class="calio-post-grid-wrap" data-pagination="' . esc_attr($pagination_mode) . '" data-initial-items="' . esc_attr((string) $initial_items) . '" data-page-size="' . esc_attr((string) $page_size) . '" data-load-more-step="' . esc_attr((string) $load_more_step) . '" data-total-items="' . esc_attr((string) $total_posts) . '">';
        echo '<div id="' . esc_attr($grid_id) . '" class="' . esc_attr(implode(' ', $classes)) . '">';
        while ($query->have_posts()) {
            $query->the_post();
            $this->render_post_card($settings, $index, ($use_load_more || $use_numbers), $use_load_more ? $initial_items : $page_size);
            $index++;
        }
        echo '</div>';
        if ($use_load_more || $use_numbers) {
            $load_more_text = trim((string) ($settings['load_more_text'] ?? esc_html__('Load More', 'calio-addon')));
            $all_loaded_text = trim((string) ($settings['all_loaded_text'] ?? esc_html__('Everything is loaded.', 'calio-addon')));
            $showing_template = __('Showing {visible} of {total} posts', 'calio-addon');
            echo '<div class="calio-post-grid-footer">';
            if ($use_load_more) {
                echo '<button class="calio-post-grid-load-more" type="button" aria-controls="' . esc_attr($grid_id) . '">' . esc_html($load_more_text !== '' ? $load_more_text : esc_html__('Load More', 'calio-addon')) . '</button>';
            }
            if ($use_numbers) {
                $prev_text = trim((string) ($settings['prev_text'] ?? esc_html__('Previous', 'calio-addon')));
                $next_text = trim((string) ($settings['next_text'] ?? esc_html__('Next', 'calio-addon')));
                echo '<div class="calio-post-grid-pagination" aria-controls="' . esc_attr($grid_id) . '" data-prev-text="' . esc_attr($prev_text !== '' ? $prev_text : esc_html__('Previous', 'calio-addon')) . '" data-next-text="' . esc_attr($next_text !== '' ? $next_text : esc_html__('Next', 'calio-addon')) . '"></div>';
            }
            echo '<div class="calio-post-grid-status" aria-live="polite" data-all-loaded-text="' . esc_attr($all_loaded_text !== '' ? $all_loaded_text : esc_html__('Everything is loaded.', 'calio-addon')) . '" data-showing-template="' . esc_attr($showing_template) . '">';
            echo esc_html(
                sprintf(
                    /* translators: 1: visible posts, 2: total posts */
                    __('Showing %1$s of %2$s posts', 'calio-addon'),
                    number_format_i18n(min($use_load_more ? $initial_items : $page_size, $total_posts)),
                    number_format_i18n($total_posts)
                )
            );
            echo '</div>';
            echo '</div>';
        }
        echo '</div>';

        wp_reset_postdata();
    }

    /**
     * Builds a safe query array from Elementor controls.
     *
     * @param array<string, mixed> $settings Widget settings.
     * @return array<string, mixed>
     */
    private function build_query_args(array $settings): array {
        $post_type = sanitize_key((string) ($settings['post_type'] ?? 'post'));
        $post_type_object = get_post_type_object($post_type);
        if (!$post_type_object || !$post_type_object->public || $post_type === 'attachment') {
            $post_type = 'post';
        }

        $orderby = sanitize_key((string) ($settings['orderby'] ?? 'date'));
        if (!in_array($orderby, ['date', 'title', 'rand', 'comment_count'], true)) {
            $orderby = 'date';
        }

        $order = strtoupper((string) ($settings['order'] ?? 'DESC'));
        if (!in_array($order, ['ASC', 'DESC'], true)) {
            $order = 'DESC';
        }

        $include_ids = $this->parse_id_list($settings['include_posts'] ?? '');
        $exclude_ids = $this->parse_id_list($settings['exclude_posts'] ?? '');

        $args = [
            'post_type' => $post_type,
            'posts_per_page' => max(1, min(24, (int) ($settings['posts_per_page'] ?? 6))),
            'offset' => max(0, (int) ($settings['offset'] ?? 0)),
            'orderby' => $orderby,
            'order' => $order,
            'post_status' => 'publish',
            'no_found_rows' => true,
        ];

        if ($post_type === 'post' && ($settings['ignore_sticky'] ?? 'yes') === 'yes') {
            $args['ignore_sticky_posts'] = true;
        }

        if (!empty($include_ids)) {
            $args['post__in'] = $include_ids;
            $args['orderby'] = 'post__in';
        }

        if (($settings['exclude_current_post'] ?? '') === 'yes' && is_singular()) {
            $exclude_ids[] = (int) get_queried_object_id();
        }

        if (!empty($exclude_ids)) {
            // User-configured post exclusion for this Elementor query.
            // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in
            $args['post__not_in'] = array_values(array_unique(array_filter(array_map('absint', $exclude_ids))));
        }

        if (!empty($settings['authors']) && is_array($settings['authors'])) {
            $args['author__in'] = array_values(array_filter(array_map('absint', $settings['authors'])));
        }

        if ($post_type === 'post') {
            $tax_query = [];
            $relation = in_array((string) ($settings['taxonomy_relation'] ?? 'OR'), ['OR', 'AND'], true) ? (string) $settings['taxonomy_relation'] : 'OR';

            if (!empty($settings['categories']) && is_array($settings['categories'])) {
                $tax_query[] = [
                    'taxonomy' => 'category',
                    'field' => 'term_id',
                    'terms' => array_values(array_filter(array_map('absint', $settings['categories']))),
                ];
            }

            if (!empty($settings['tags']) && is_array($settings['tags'])) {
                $tax_query[] = [
                    'taxonomy' => 'post_tag',
                    'field' => 'term_id',
                    'terms' => array_values(array_filter(array_map('absint', $settings['tags']))),
                ];
            }

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

                // User-selected taxonomy filters for this Elementor widget.
                // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
                $args['tax_query'] = $tax_query;
            }
        }

        return $args;
    }

    /**
     * Prints a single post card with optional media, meta, excerpt and CTA.
     *
     * @param array<string, mixed> $settings Widget settings.
     */
    private function render_post_card(array $settings, int $index, bool $use_load_more = false, int $initial_items = 0): void {
        $showImage = ($settings['show_image'] ?? 'yes') === 'yes';
        $showCategory = ($settings['show_category'] ?? 'yes') === 'yes' && get_post_type() === 'post';
        $showMeta = ($settings['show_meta'] ?? 'yes') === 'yes';
        $showComments = $showMeta && (($settings['show_comments'] ?? '') === 'yes') && get_post_type() === 'post';
        $showReadTime = $showMeta && (($settings['show_read_time'] ?? '') === 'yes') && (($settings['show_excerpt'] ?? 'yes') === 'yes');
        $showAuthor = $showMeta && (($settings['show_author'] ?? 'yes') === 'yes');
        $showDate = $showMeta && (($settings['show_date'] ?? 'yes') === 'yes');
        $showExcerpt = ($settings['show_excerpt'] ?? 'yes') === 'yes';
        $showButton = ($settings['show_button'] ?? 'yes') === 'yes';
        $excerptLength = max(5, min(80, (int) ($settings['excerpt_length'] ?? 18)));
        $buttonText = trim((string) ($settings['button_text'] ?? esc_html__('Read More', 'calio-addon')));
        $featuredBadgeText = trim((string) ($settings['featured_badge_text'] ?? esc_html__('Featured', 'calio-addon')));
        $eyebrow = $this->get_eyebrow_text((string) ($settings['eyebrow_source'] ?? 'category'));
        $is_featured = (($settings['feature_first'] ?? 'yes') === 'yes') && ($index === 0);
        $card_classes = 'calio-post-grid-card' . ($is_featured ? ' is-featured' : '');
        $is_hidden = $use_load_more && ($index >= $initial_items);

        echo '<article class="' . esc_attr($card_classes) . '" data-post-index="' . esc_attr((string) $index) . '"' . ($is_hidden ? ' hidden' : '') . '>';
        if ($showImage && has_post_thumbnail()) {
            echo '<a class="calio-post-grid-media" href="' . esc_url(get_permalink()) . '">';
            echo wp_kses_post(get_the_post_thumbnail(get_the_ID(), 'large', ['loading' => 'lazy']));
            echo '</a>';
        }
        echo '<div class="calio-post-grid-content">';
        if ($eyebrow !== '') {
            echo '<div class="calio-post-grid-eyebrow">' . esc_html($eyebrow) . '</div>';
        }
        if ($showCategory) {
            $terms = get_the_terms(get_the_ID(), 'category');
            if (is_array($terms) && !empty($terms)) {
                echo '<div class="calio-post-grid-category-row">';
                foreach (array_slice($terms, 0, 2) as $term) {
                    echo '<span class="calio-post-grid-category">' . esc_html($term->name) . '</span>';
                }
                echo '</div>';
            }
        }
        if ($is_featured && $featuredBadgeText !== '') {
            echo '<div class="calio-post-grid-featured-badge">' . esc_html($featuredBadgeText) . '</div>';
        }
        if ($showMeta && ($showAuthor || $showDate || $showComments || $showReadTime)) {
            echo '<div class="calio-post-grid-meta">';
            if ($showAuthor) {
                echo '<span class="calio-post-grid-meta-item">' . esc_html(get_the_author()) . '</span>';
            }
            if ($showDate) {
                echo '<span class="calio-post-grid-meta-item">' . esc_html(get_the_date()) . '</span>';
            }
            if ($showComments) {
                $comments_count = get_comments_number();
                echo '<span class="calio-post-grid-meta-item">' . esc_html(
                    sprintf(
                        /* translators: %s: comment count. */
                        _n('%s comment', '%s comments', $comments_count, 'calio-addon'),
                        number_format_i18n($comments_count)
                    )
                ) . '</span>';
            }
            if ($showReadTime) {
                echo '<span class="calio-post-grid-meta-item">' . esc_html($this->get_read_time()) . '</span>';
            }
            echo '</div>';
        }
        echo '<h3 class="calio-post-grid-title"><a href="' . esc_url(get_permalink()) . '">' . esc_html(get_the_title()) . '</a></h3>';
        if ($showExcerpt) {
            echo '<div class="calio-post-grid-excerpt">' . esc_html(wp_trim_words(wp_strip_all_tags(get_the_excerpt()), $excerptLength)) . '</div>';
        }
        if ($showButton && $buttonText !== '') {
            echo '<a class="calio-post-grid-button" href="' . esc_url(get_permalink()) . '">' . esc_html($buttonText) . '</a>';
        }
        echo '</div>';
        echo '</article>';
    }

    /**
     * Estimates reading time from the current post content for quick grid metadata.
     */
    private function get_read_time(): string {
        $content = wp_strip_all_tags((string) get_the_content());
        $words = max(1, str_word_count($content));
        $minutes = max(1, (int) ceil($words / 220));

        return sprintf(
            /* translators: %d: estimated reading time in minutes. */
            _n('%d min read', '%d mins read', $minutes, 'calio-addon'),
            $minutes
        );
    }

    /**
     * Resolves a lightweight eyebrow label for each card.
     */
    private function get_eyebrow_text(string $source): string {
        if ($source === 'none') {
            return '';
        }

        if ($source === 'date') {
            return (string) get_the_date();
        }

        if ($source === 'post_type') {
            $post_type_object = get_post_type_object((string) get_post_type());
            return $post_type_object ? (string) $post_type_object->labels->singular_name : '';
        }

        if ($source === 'category' && get_post_type() === 'post') {
            $terms = get_the_terms(get_the_ID(), 'category');
            if (is_array($terms) && !empty($terms)) {
                return (string) $terms[0]->name;
            }
        }

        return '';
    }

    /**
     * Returns public post types that make sense in a content grid.
     *
     * @return array<string, string>
     */
    private function get_post_type_options(): array {
        $options = [];
        foreach (get_post_types(['public' => true], 'objects') as $postType) {
            if ($postType->name === 'attachment') {
                continue;
            }
            $options[$postType->name] = $postType->labels->singular_name;
        }

        return $options;
    }

    /**
     * Returns author options for query filtering.
     *
     * @return array<int, string>
     */
    private function get_author_options(): array {
        $options = [];

        foreach (get_users([
            'capability' => 'edit_posts',
            'orderby' => 'display_name',
            'order' => 'ASC',
        ]) as $user) {
            $options[(int) $user->ID] = $user->display_name;
        }

        return $options;
    }

    /**
     * Returns category options for post filtering.
     *
     * @return array<int, string>
     */
    private function get_category_options(): array {
        $options = [];
        $terms = get_terms(['taxonomy' => 'category', 'hide_empty' => false]);
        if (is_wp_error($terms) || !is_array($terms)) {
            return $options;
        }

        foreach ($terms as $term) {
            $options[(int) $term->term_id] = $term->name;
        }

        return $options;
    }

    /**
     * Returns tag options for post filtering.
     *
     * @return array<int, string>
     */
    private function get_tag_options(): array {
        $options = [];
        $terms = get_terms(['taxonomy' => 'post_tag', 'hide_empty' => false]);
        if (is_wp_error($terms) || !is_array($terms)) {
            return $options;
        }

        foreach ($terms as $term) {
            $options[(int) $term->term_id] = $term->name;
        }

        return $options;
    }

    /**
     * Parses a comma-separated list of IDs from a text control.
     *
     * @param mixed $value Raw control value.
     * @return array<int, int>
     */
    private function parse_id_list($value): array {
        if (!is_string($value) || trim($value) === '') {
            return [];
        }

        return array_values(
            array_filter(
                array_map('absint', preg_split('/\s*,\s*/', trim($value)) ?: [])
            )
        );
    }
}

