<?php
/**
 * Frontend Render
 */

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

// [product_filter] renders the attribute filter only.
// [product_filter_search] renders the same filter plus the inline AI text search.
// Both accept the same id="".
add_shortcode('product_filter', 'askagento_shortcode');
add_shortcode('product_filter_search', 'askagento_shortcode_search');
add_action('wp_enqueue_scripts', 'askagento_frontend_scripts');

/**
 * Resolve filter post ID from shortcode id="" when it is not a plain integer (e.g. merchants paste the filter title).
 *
 * @param mixed $raw Shortcode id attribute.
 * @return int Filter post ID or 0.
 */
function askagento_resolve_product_filter_post_id($raw) {
    if ($raw === null || $raw === false) {
        return 0;
    }
    $s = trim((string) $raw);
    if ($s === '') {
        return 0;
    }
    // Positive integer only - avoids (int) "AI Generated Filter" => 0 bugs when combined with title lookup.
    if (preg_match('/^\d+$/', $s)) {
        $id = (int) $s;

        return $id > 0 ? $id : 0;
    }

    $needle = $s;
    $slug = sanitize_title($needle);

    $candidates = get_posts(
        [
            'post_type'        => ASKAGENTO_POST_TYPE,
            'post_status'      => ['publish', 'draft', 'pending', 'private'],
            'posts_per_page'   => 200,
            'orderby'          => 'ID',
            'order'            => 'DESC',
            'suppress_filters' => false,
        ]
    );

    foreach ($candidates as $post) {
        if (strcasecmp((string) $post->post_title, $needle) === 0) {
            return (int) $post->ID;
        }
    }

    if ($slug !== '') {
        foreach ($candidates as $post) {
            if ($post->post_name === $slug) {
                return (int) $post->ID;
            }
        }
    }

    return 0;
}

/**
 * Enqueue frontend scripts
 */
function askagento_frontend_scripts() {
    wp_register_style('askagento-front', ASKAGENTO_PLUGIN_URL . 'assets/css/front.css', [], askagento_asset_version('assets/css/front.css'));
    wp_register_script('askagento-front', ASKAGENTO_PLUGIN_URL . 'assets/js/front.js', [], askagento_asset_version('assets/js/front.js'), true);
}

/**
 * Shortcode handler
 */
function askagento_shortcode($atts) {
    return askagento_render_filter_shortcode($atts, false);
}

/**
 * [product_filter_search] — same filter plus the inline AI text search.
 */
function askagento_shortcode_search($atts) {
    return askagento_render_filter_shortcode($atts, true);
}

/**
 * Shared handler for [product_filter] / [product_filter_search].
 *
 * @param array $atts        Shortcode attributes.
 * @param bool  $with_search Whether to include the inline AI text search.
 * @return string
 */
function askagento_render_filter_shortcode($atts, $with_search) {
    $atts = shortcode_atts(['id' => 0], $atts);
    $raw = isset($atts['id']) ? $atts['id'] : 0;
    $filter_id = askagento_resolve_product_filter_post_id($raw);
    /**
     * Resolved filter post ID for [product_filter] (after numeric / title / slug resolution).
     *
     * @param int   $filter_id Resolved ID (may be 0).
     * @param array $atts      Shortcode attributes.
     */
    $filter_id = (int) apply_filters('askagento_product_filter_shortcode_post_id', $filter_id, $atts);

    if (!$filter_id) {
        return '';
    }

    $post = get_post($filter_id);
    if (!$post || $post->post_type !== ASKAGENTO_POST_TYPE) {
        return '';
    }

    return askagento_render_filter($filter_id, $with_search);
}

/**
 * Render filter HTML
 *
 * @param int  $filter_id
 * @param bool $with_search Include the inline AI text search (via [product_filter_search]).
 */
function askagento_render_filter($filter_id, $with_search = false) {
    $fields = askagento_get_fields($filter_id);
    $settings = askagento_get_settings($filter_id);
    
    if (empty($fields)) {
        return '';
    }

    if (function_exists('askagento_maybe_schedule_filter_cache_from_frontend')) {
        askagento_maybe_schedule_filter_cache_from_frontend((int) $filter_id);
    }

    wp_enqueue_style('askagento-front');
    wp_enqueue_script('askagento-front');
    
    // Get current values from URL
    $current = askagento_get_current_values();
    
    // Calculate counts
    $counts = askagento_calculate_counts($filter_id, $current);
    $total = askagento_get_total_count($filter_id, $current);
    
    $local = [
        'ajaxUrl'  => admin_url('admin-ajax.php'),
        'nonce'    => wp_create_nonce('askagento_filter'),
        'filterId' => $filter_id,
        'selector' => $settings['results_selector'],
        'ajax'     => $settings['ajax_enabled'],
        'counts'   => $counts,
    ];

    if ($settings['ajax_enabled']) {
        $local['filterRegionId'] = 'askagento-filter-region-' . $filter_id;
    }

    if (
        $with_search
        && function_exists('askagento_ai_text_search_is_available')
        && askagento_ai_text_search_is_available()
    ) {
        $local['aiTextSearch'] = [
            'nonce' => wp_create_nonce('askagento_ai_text_search'),
        ];
    }

    wp_localize_script('askagento-front', 'askagentoFront', $local);

    $askagento_js_config = [
        'ajaxUrl'  => admin_url('admin-ajax.php'),
        'nonce'    => wp_create_nonce('askagento_filter'),
        'filterId' => (int) $filter_id,
        'selector' => $settings['results_selector'],
        'ajax'     => ! empty($settings['ajax_enabled']),
    ];
    if (! empty($settings['ajax_enabled'])) {
        $askagento_js_config['filterRegionId'] = 'askagento-filter-region-' . $filter_id;
    }

    ob_start();
    ?>
    <form class="askagento-filter" data-filter-id="<?php echo esc_attr((string) $filter_id); ?>" data-askagento-config="<?php echo esc_attr(wp_json_encode($askagento_js_config)); ?>" method="get">
        <?php
        if (!empty($local['aiTextSearch']) && function_exists('askagento_get_ai_text_search_inner_markup')) {
            // Connected mode: the search box narrows this filter's results grid.
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- markup builder escapes internally.
            echo askagento_get_ai_text_search_inner_markup((string) $filter_id, true);
        }
        ?>

        <div class="askagento-total">
            <?php
            echo wp_kses(
                sprintf(
                    /* translators: %d: Number of matching products. */
                    __( 'Found: <strong>%d</strong> products', 'askagento' ),
                    (int) $total
                ),
                [
                    'strong' => [],
                ]
            );
            ?>
        </div>
        
        <?php foreach ($fields as $field): ?>
            <?php askagento_render_field($field, $settings, $current, $counts); ?>
        <?php endforeach; ?>

        <?php if ($settings['ajax_enabled']) : ?>
            <p class="description askagento-filter-ajax-hint" style="margin:12px 0 8px;font-size:13px;color:#646970;">
                <?php esc_html_e('Filters apply automatically shortly after you change them (live update). If the product list does not change, click Apply filters.', 'askagento'); ?>
            </p>
            <div class="askagento-submit askagento-submit-ajax">
                <button type="button" class="askagento-btn askagento-apply-filters-btn"><?php esc_html_e('Apply filters', 'askagento'); ?></button>
            </div>
        <?php endif; ?>

        <?php if (!$settings['ajax_enabled']): ?>
            <div class="askagento-submit">
                <button type="submit" class="askagento-btn"><?php esc_html_e('Apply', 'askagento'); ?></button>
            </div>
        <?php endif; ?>
        
        <?php if (askagento_has_active_filters($current)): ?>
            <div class="askagento-reset">
                <a href="<?php echo esc_url(askagento_get_reset_url()); ?>" class="askagento-reset-link"><?php esc_html_e('Reset filters', 'askagento'); ?></a>
            </div>
        <?php endif; ?>
    </form>
    <?php if ($settings['ajax_enabled']) : ?>
        <div id="<?php echo esc_attr('askagento-filter-region-' . (int) $filter_id); ?>" class="woocommerce askagento-filter-products-region">
            <?php
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- product list HTML is built with escaped WooCommerce output.
            echo askagento_render_filtered_products_inner_html($filter_id, $current);
            ?>
        </div>
    <?php endif; ?>
    <?php
    return ob_get_clean();
}

/**
 * Render single field
 */
function askagento_render_field($field, $settings, $current, $counts = []) {
    $source = $field['source'];
    $type = $field['type'];
    $label = $field['label'];
    $show_count = $field['show_count'] && $settings['show_count'];
    
    // Parse source
    $parts = explode(':', $source, 2);
    if (count($parts) !== 2) return;
    
    $source_type = $parts[0];
    $source_name = $parts[1];
    
    // Get field name for form
    $field_name = askagento_get_field_name($source_type, $source_name);
    
    // Get options
    $options = askagento_get_field_options($source_type, $source_name, $settings);

    // A numeric/range data source always returns a range descriptor
    // ([ 'min' => ..., 'max' => ..., 'step' => ... ]) rather than option rows.
    // If such a field was saved with a choice type (checkbox/radio/dropdown) —
    // e.g. by the auto-generator — force range rendering. Feeding range data to
    // the choice renderers raises PHP warnings that, with display_errors on, get
    // injected into the option markup and corrupt the whole widget (a broken
    // <select> can swallow the following field in the browser).
    if (is_array($options) && isset($options['min'], $options['max'], $options['step'])) {
        $type = 'range';
    }

    // Get counts for this field
    $field_counts = isset($counts[$field_name]) ? $counts[$field_name] : [];

    // Choice fields use a list of [ 'value' => ..., 'label' => ..., 'count' => ... ].
    // Range fields use [ 'min' => ..., 'max' => ..., 'step' => ... ] - do not treat as option rows.
    if ($type !== 'range' && ! empty($field_counts) && is_array($options)) {
        foreach ($options as &$opt) {
            if (is_array($opt) && isset($opt['value'], $field_counts[$opt['value']])) {
                $opt['count'] = $field_counts[$opt['value']];
            }
        }
        unset($opt);

        // Hide empty if enabled
        if ($settings['hide_empty']) {
            $options = array_filter($options, function ($opt) {
                return is_array($opt) && isset($opt['count']) && (int) $opt['count'] > 0;
            });
        }
    }
    
    if (empty($options) && $type !== 'range') {
        return;
    }
    
    // Current value
    $value = isset($current[$field_name]) ? $current[$field_name] : '';
    
    ?>
    <div class="askagento-field askagento-field-<?php echo esc_attr($type); ?>" data-field="<?php echo esc_attr($field_name); ?>">
        <div class="askagento-field-label"><?php echo esc_html($label); ?></div>
        <div class="askagento-field-options">
            <?php
            switch ($type) {
                case 'checkbox':
                    askagento_render_checkbox($field_name, $options, $value, $show_count);
                    break;
                case 'radio':
                    askagento_render_radio($field_name, $options, $value, $show_count);
                    break;
                case 'dropdown':
                    askagento_render_dropdown($field_name, $options, $value, $show_count, $label);
                    break;
                case 'range':
                    askagento_render_range($field_name, $options, $value, $field_counts);
                    break;
            }
            ?>
        </div>
    </div>
    <?php
}

/**
 * Render checkbox field
 */
function askagento_render_checkbox($name, $options, $value, $show_count) {
    $values = is_array($value) ? $value : ($value ? explode(',', $value) : []);
    
    foreach ($options as $opt) {
        if (!is_array($opt) || !isset($opt['value'])) {
            continue;
        }
        $checked = in_array($opt['value'], $values) ? 'checked' : '';
        $count = isset($opt['count']) ? (int) $opt['count'] : 0;
        $disabled = ($count === 0 && !$checked) ? ' disabled' : '';
        ?>
        <label class="askagento-option askagento-checkbox<?php echo esc_attr($count === 0 ? ' askagento-empty' : ''); ?>" data-value="<?php echo esc_attr($opt['value']); ?>">
            <input type="checkbox" name="<?php echo esc_attr($name); ?>[]" value="<?php echo esc_attr($opt['value']); ?>" <?php echo esc_attr($checked); ?><?php echo esc_attr($disabled); ?>>
            <span class="askagento-option-label"><?php echo esc_html($opt['label']); ?><?php if ($show_count) : ?> <span class="askagento-count" data-value="<?php echo esc_attr($opt['value']); ?>">(<?php echo absint($count); ?>)</span><?php endif; ?></span>
        </label>
        <?php
    }
}

/**
 * Render radio field
 */
function askagento_render_radio($name, $options, $value, $show_count) {
    ?>
    <label class="askagento-option askagento-radio" data-value="">
        <input type="radio" name="<?php echo esc_attr($name); ?>" value="" <?php checked($value, ''); ?>>
        <span class="askagento-option-label"><?php esc_html_e('All', 'askagento'); ?></span>
    </label>
    <?php
    foreach ($options as $opt) {
        if (!is_array($opt) || !isset($opt['value'])) {
            continue;
        }
        $count = isset($opt['count']) ? (int) $opt['count'] : 0;
        $disabled = ($count === 0 && $value !== $opt['value']) ? ' disabled' : '';
        ?>
        <label class="askagento-option askagento-radio<?php echo esc_attr($count === 0 ? ' askagento-empty' : ''); ?>" data-value="<?php echo esc_attr($opt['value']); ?>">
            <input type="radio" name="<?php echo esc_attr($name); ?>" value="<?php echo esc_attr($opt['value']); ?>" <?php checked($value, $opt['value']); ?><?php echo esc_attr($disabled); ?>>
            <span class="askagento-option-label"><?php echo esc_html($opt['label']); ?><?php if ($show_count) : ?> <span class="askagento-count" data-value="<?php echo esc_attr($opt['value']); ?>">(<?php echo absint($count); ?>)</span><?php endif; ?></span>
        </label>
        <?php
    }
}

/**
 * Render dropdown field
 */
function askagento_render_dropdown($name, $options, $value, $show_count, $label) {
    ?>
    <select name="<?php echo esc_attr($name); ?>" class="askagento-select">
        <option value=""><?php echo esc_html($label); ?>...</option>
        <?php foreach ($options as $opt): ?>
            <?php
            if (!is_array($opt) || !isset($opt['value'])) {
                continue;
            }
            $count = isset($opt['count']) ? (int) $opt['count'] : 0;
            $count_txt = $show_count ? ' (' . $count . ')' : ''; 
            $disabled = ($count === 0 && $value !== $opt['value']) ? ' disabled' : '';
            ?>
            <option value="<?php echo esc_attr($opt['value']); ?>" <?php selected($value, $opt['value']); ?><?php echo esc_attr($disabled); ?> data-count="<?php echo absint($count); ?>">
                <?php echo esc_html($opt['label'] . $count_txt); ?>
            </option>
        <?php endforeach; ?>
    </select>
    <?php
}

/**
 * Render range field
 */
function askagento_render_range($name, $options, $value, $dynamic = []) {
    $min = isset($options['min']) ? $options['min'] : 0;
    $max = isset($options['max']) ? $options['max'] : 1000;
    $step = isset($options['step']) ? $options['step'] : 1;
    
    // Use dynamic bounds if available
    if (!empty($dynamic)) {
        $dyn_min = isset($dynamic['min']) ? $dynamic['min'] : $min;
        $dyn_max = isset($dynamic['max']) ? $dynamic['max'] : $max;
    } else {
        $dyn_min = $min;
        $dyn_max = $max;
    }
    
    // Parse current values
    $val_min = $min;
    $val_max = $max;
    if ($value) {
        $parts = explode('-', $value);
        if (count($parts) === 2) {
            $val_min = (float) $parts[0];
            $val_max = (float) $parts[1];
        }
    }
    ?>
    <div class="askagento-range-wrap" data-min="<?php echo esc_attr($min); ?>" data-max="<?php echo esc_attr($max); ?>" data-step="<?php echo esc_attr($step); ?>">
        <div class="askagento-range-inputs">
            <input type="number" class="askagento-range-min" name="<?php echo esc_attr($name); ?>_min"
                   value="<?php echo esc_attr($val_min); ?>" min="<?php echo esc_attr($min); ?>" max="<?php echo esc_attr($max); ?>" step="<?php echo esc_attr($step); ?>"
                   placeholder="<?php echo esc_attr($dyn_min); ?>">
            <span class="askagento-range-sep">-</span>
            <input type="number" class="askagento-range-max" name="<?php echo esc_attr($name); ?>_max"
                   value="<?php echo esc_attr($val_max); ?>" min="<?php echo esc_attr($min); ?>" max="<?php echo esc_attr($max); ?>" step="<?php echo esc_attr($step); ?>"
                   placeholder="<?php echo esc_attr($dyn_max); ?>">
        </div>
        <div class="askagento-range-hint">
            <span class="askagento-range-available"><?php
            /* translators: 1: minimum available value, 2: maximum available value */
            printf(esc_html__('Available: %1$s - %2$s', 'askagento'), esc_html($dyn_min), esc_html($dyn_max));
            ?></span>
        </div>
        <input type="hidden" name="<?php echo esc_attr($name); ?>" class="askagento-range-value" value="<?php echo esc_attr($value); ?>">
    </div>
    <?php
}

/**
 * Get field name for URL
 */
function askagento_get_field_name($source_type, $source_name) {
    switch ($source_type) {
        case 'taxonomy':
            return ASKAGENTO_TAX_PREFIX . $source_name;
        case 'meta':
            return ASKAGENTO_META_PREFIX . $source_name;
        case 'wc':
            return ASKAGENTO_NUM_PREFIX . $source_name;
        default:
            return $source_name;
    }
}

/**
 * Get field options
 */
function askagento_get_field_options($source_type, $source_name, $settings) {
    $post_types = $settings['post_types'];
    $hide_empty = $settings['hide_empty'];
    
    switch ($source_type) {
        case 'taxonomy':
            return askagento_get_taxonomy_options($source_name, $hide_empty);
        case 'meta':
            return askagento_get_meta_options($source_name, $post_types, $hide_empty);
        case 'wc':
            return askagento_get_wc_options($source_name, $post_types);
        default:
            return [];
    }
}

/**
 * Get taxonomy options
 */
function askagento_get_taxonomy_options($taxonomy, $hide_empty) {
    $terms = get_terms([
        'taxonomy'   => $taxonomy,
        'hide_empty' => $hide_empty,
        'orderby'    => 'name',
        'order'      => 'ASC',
    ]);
    
    if (is_wp_error($terms)) {
        return [];
    }
    
    $options = [];
    foreach ($terms as $term) {
        $options[] = [
            'value' => $term->slug,
            'label' => $term->name,
            'count' => $term->count,
        ];
    }
    
    return $options;
}

/**
 * Get meta options
 */
function askagento_get_meta_options($meta_key, $post_types, $hide_empty) {
    global $wpdb;
    
    $post_types = !empty($post_types) && is_array($post_types) ? array_values($post_types) : ['product'];
    $placeholders = implode(', ', array_fill(0, count($post_types), '%s'));

    // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Direct query required to fetch distinct meta values with post counts for rendering filter options on the storefront. $placeholders holds only generated %s tokens (array_fill over count()); the meta key and post types are bound through the array passed to prepare(), which the sniff cannot count statically.
    $results = $wpdb->get_results($wpdb->prepare("
        SELECT pm.meta_value as value, COUNT(DISTINCT pm.post_id) as count
        FROM {$wpdb->postmeta} pm
        INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID
        WHERE pm.meta_key = %s
          AND pm.meta_value != ''
          AND p.post_type IN ({$placeholders})
          AND p.post_status = 'publish'
        GROUP BY pm.meta_value
        ORDER BY pm.meta_value ASC
    ", array_merge([$meta_key], $post_types)));
    // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber

    $options = [];
    foreach ($results as $row) {
        if ($hide_empty && $row->count == 0) {
            continue;
        }
        $options[] = [
            'value' => $row->value,
            'label' => $row->value,
            'count' => (int) $row->count,
        ];
    }
    
    return $options;
}

/**
 * Get WooCommerce options (price range, stock)
 */
function askagento_get_wc_options($field, $post_types) {
    global $wpdb;

    $wc_meta = askagento_wc_source_to_meta_key($field);

    if ($wc_meta === '_price') {
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query required to retrieve WooCommerce price min/max bounds for the price range filter on the storefront.
        $stats = $wpdb->get_row("
            SELECT MIN(CAST(pm.meta_value AS DECIMAL(10,2))) as min_val,
                   MAX(CAST(pm.meta_value AS DECIMAL(10,2))) as max_val
            FROM {$wpdb->postmeta} pm
            INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID
            WHERE pm.meta_key = '_price'
              AND pm.meta_value != ''
              AND pm.meta_value REGEXP '^[0-9]+\.?[0-9]*$'
              AND p.post_status = 'publish'
              AND p.post_type IN ('product', 'product_variation')
        ");

        return [
            'min'  => 0,
            'max'  => ceil($stats->max_val ?? 1000),
            'step' => 1,
        ];
    }

    if ($wc_meta === '_stock_status') {
        return [
            ['value' => 'instock', 'label' => __('In Stock', 'askagento'), 'count' => 0],
            ['value' => 'outofstock', 'label' => __('Out of Stock', 'askagento'), 'count' => 0],
            ['value' => 'onbackorder', 'label' => __('On Backorder', 'askagento'), 'count' => 0],
        ];
    }
    
    return [];
}

/**
 * Sanitize a single filter value from GET/AJAX. Taxonomy facets must stay slug-safe (not sanitize_text_field).
 *
 * @param string $param_key e.g. _askagento_t_pa_color
 * @param mixed  $raw
 * @return string
 */
function askagento_sanitize_single_filter_value($param_key, $raw) {
    $raw = wp_unslash($raw);
    $raw = is_string($raw) ? $raw : (string) $raw;

    if (strpos($param_key, ASKAGENTO_TAX_PREFIX) === 0) {
        $raw = rawurldecode($raw);

        return sanitize_title($raw);
    }

    return sanitize_text_field($raw);
}

/**
 * Get current filter values from URL
 */
function askagento_get_current_values() {
    $values = [];
    
    // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only frontend filter URL parameters; sanitized via prefix allowlist and value sanitizers.
    foreach ($_GET as $key => $val) {
        if (strpos($key, '_askagento_t_') === 0 || strpos($key, '_askagento_m_') === 0 || strpos($key, '_askagento_n_') === 0) {
            $safe_key = sanitize_text_field((string) $key);
            if ($safe_key === '') {
                continue;
            }
            if (is_array($val)) {
                $values[$safe_key] = array_values(
                    array_filter(
                        array_map(
                            static function ($v) use ($safe_key) {
                                return askagento_sanitize_single_filter_value($safe_key, $v);
                            },
                            $val
                        )
                    )
                );
            } else {
                $values[$safe_key] = askagento_sanitize_single_filter_value($safe_key, $val);
            }
        }
    }

    // Handle range fields
    foreach ($_GET as $key => $val) {
        if (preg_match('/^(_askagento_[tmn]_.+)_(min|max)$/', $key, $m)) {
            $base = $m[1];
            if (!isset($values[$base])) {
                $min = isset($_GET[$base . '_min']) ? (float) $_GET[$base . '_min'] : '';
                $max = isset($_GET[$base . '_max']) ? (float) $_GET[$base . '_max'] : '';
                if ($min !== '' || $max !== '') {
                    $values[$base] = $min . '-' . $max;
                }
            }
        }
    }
    // phpcs:enable WordPress.Security.NonceVerification.Recommended

    return $values;
}

/**
 * Check if any filters active
 */
function askagento_has_active_filters($current) {
    return !empty($current);
}

/**
 * Get reset URL
 */
function askagento_get_reset_url() {
    $url = remove_query_arg(array_keys(askagento_get_current_values()));
    return $url ?: get_permalink();
}
