<?php

/**
 * AJAX Handler for Havenlytics
 *
 * @package     Havenlytics
 * @subpackage  Frontend
 * @copyright   Copyright (c) 2025, Havenlytics
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       2.0.0
 */

namespace HvnlyNab\Frontend;

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

/**
 * AJAX Handler class
 *
 * @since 2.0.0
 */
class AjaxHandler
{
    /**
     * Cache duration for search results
     *
     * @var int
     */
    private $search_cache_duration = 1 * HOUR_IN_SECONDS;

    /**
     * Property Builder sections cache
     *
     * @var array|null
     */
    private $property_builder_sections = null;

    /**
     * Map field definitions cache (field_name => field_type)
     *
     * @var array
     */
    private $map_field_definitions = [];

    /**
     * Constructor
     */
    public function __construct()
    {
        add_action('wp_ajax_hvnly_search_properties', array($this, 'search_properties'));
        add_action('wp_ajax_nopriv_hvnly_search_properties', array($this, 'search_properties'));

        add_action('wp_ajax_hvnly_load_more', array($this, 'load_more_properties'));
        add_action('wp_ajax_nopriv_hvnly_load_more', array($this, 'load_more_properties'));

        add_action('wp_ajax_hvnly_get_filters', array($this, 'get_filter_options'));
        add_action('wp_ajax_nopriv_hvnly_get_filters', array($this, 'get_filter_options'));

        add_action('wp_ajax_hvnly_get_properties_for_map', array($this, 'hvnly_get_properties_for_map'));
        add_action('wp_ajax_nopriv_hvnly_get_properties_for_map', array($this, 'hvnly_get_properties_for_map'));

        add_action('save_post_hvnly_property', array($this, 'clear_property_cache'));
        add_action('delete_post', array($this, 'clear_property_cache'));
        add_action('created_term', array($this, 'clear_terms_cache'), 10, 2);
        add_action('edited_term', array($this, 'clear_terms_cache'), 10, 2);
        add_action('delete_term', array($this, 'clear_terms_cache'), 10, 3);

        add_action('created_term', array($this, 'clear_sidebar_terms_cache'), 10, 2);
        add_action('edited_term', array($this, 'clear_sidebar_terms_cache'), 10, 2);
        add_action('delete_term', array($this, 'clear_sidebar_terms_cache'), 10, 3);

        add_action('wp_ajax_hvnly_render_map_container', array($this, 'hvnly_render_map_container'));
        add_action('wp_ajax_nopriv_hvnly_render_map_container', array($this, 'hvnly_render_map_container'));
    }

    /**
     * Get Property Builder sections from database
     *
     * @return array
     */
    private function get_property_builder_sections()
    {
        if ($this->property_builder_sections === null) {
            $this->property_builder_sections = get_option('hvnly_property_builder.sections', array());
            
            if (empty($this->property_builder_sections) || !is_array($this->property_builder_sections)) {
                $this->property_builder_sections = array();
            }
        }
        return $this->property_builder_sections;
    }

    /**
     * Get all map field definitions from Property Builder
     *
     * @return array
     */
    private function get_map_field_definitions()
    {
        if (!empty($this->map_field_definitions)) {
            return $this->map_field_definitions;
        }

        $sections = $this->get_property_builder_sections();
        $map_fields = array();

        foreach ($sections as $section) {
            if (empty($section['fields']) || !is_array($section['fields'])) {
                continue;
            }

            foreach ($section['fields'] as $field) {
                $is_map_field = false;
                if (isset($field['type']) && $field['type'] === 'map') {
                    $is_map_field = true;
                }
                if (isset($field['group_type']) && $field['group_type'] === 'map') {
                    $is_map_field = true;
                }

                if ($is_map_field && isset($field['name'])) {
                    $field_name = $field['name'];
                    $base_name = preg_replace('/_(address|latitude|longitude|preview)$/', '', $field_name);
                    
                    if (!empty($base_name)) {
                        $map_fields[$base_name] = array(
                            'base_id' => $base_name,
                            'address_key' => $base_name . '_address',
                            'latitude_key' => $base_name . '_latitude',
                            'longitude_key' => $base_name . '_longitude',
                            'preview_key' => $base_name . '_preview',
                        );
                    }
                }
            }
        }

        $this->map_field_definitions = $map_fields;
        return $map_fields;
    }

    /**
     * Get property coordinates from Property Builder map fields
     * Uses stored references to find the correct dynamic field names
     *
     * @param int $property_id Property ID
     * @return array ['lat' => float, 'lng' => float, 'address' => string, 'has_coordinates' => bool]
     */
    private function get_property_map_data($property_id)
    {
        // Default return
        $result = array(
            'lat' => null,
            'lng' => null,
            'address' => '',
            'has_coordinates' => false
        );

        // ============================================================
        // METHOD 1: Check stored reference keys (MOST RELIABLE)
        // These tell us exactly which dynamic field names to use
        // ============================================================
        $active_lat_key = get_post_meta($property_id, '_hvnly_active_map_latitude_key', true);
        $active_lng_key = get_post_meta($property_id, '_hvnly_active_map_longitude_key', true);
        $active_address_key = get_post_meta($property_id, '_hvnly_active_map_address_key', true);
        
        if (!empty($active_lat_key) && !empty($active_lng_key)) {
            $lat = get_post_meta($property_id, $active_lat_key, true);
            $lng = get_post_meta($property_id, $active_lng_key, true);
            $address = get_post_meta($property_id, $active_address_key, true);
            
            if (!empty($lat) && is_numeric($lat) && !empty($lng) && is_numeric($lng)) {
                $result['lat'] = (float) $lat;
                $result['lng'] = (float) $lng;
                $result['address'] = $address;
                $result['has_coordinates'] = true;
                
                // if (defined('WP_DEBUG') && WP_DEBUG) {
                //     error_log("Havenlytics Map: Found coordinates using stored references for property $property_id: $active_lat_key=$lat, $active_lng_key=$lng");
                // }
                return $result;
            }
        }
        
        // ============================================================
        // METHOD 2: Search ALL meta keys for map_*_latitude/longitude pattern
        // This finds dynamic fields even without stored references
        // ============================================================
        $all_meta = get_post_meta($property_id);
        $found_base = null;
        $found_lat = null;
        $found_lng = null;
        $found_address = null;
        
        foreach ($all_meta as $meta_key => $meta_value) {
            // Look for latitude pattern
            if (preg_match('/^map_.+_latitude$/i', $meta_key)) {
                $lat_value = is_array($meta_value) ? ($meta_value[0] ?? '') : $meta_value;
                
                if (!empty($lat_value) && is_numeric($lat_value)) {
                    // Find corresponding longitude using same base
                    $base_key = preg_replace('/_latitude$/i', '', $meta_key);
                    $lng_key = $base_key . '_longitude';
                    $address_key = $base_key . '_address';
                    
                    $lng_value = get_post_meta($property_id, $lng_key, true);
                    $address_value = get_post_meta($property_id, $address_key, true);
                    
                    if (!empty($lng_value) && is_numeric($lng_value)) {
                        $found_base = $base_key;
                        $found_lat = (float) $lat_value;
                        $found_lng = (float) $lng_value;
                        $found_address = $address_value;
                        break;
                    }
                }
            }
        }
        
        if ($found_lat !== null && $found_lng !== null) {
            $result['lat'] = $found_lat;
            $result['lng'] = $found_lng;
            $result['address'] = $found_address;
            $result['has_coordinates'] = true;
            
            // Cache the found base for future use
            if (!empty($found_base)) {
                update_post_meta($property_id, '_hvnly_active_map_field_base', $found_base);
                update_post_meta($property_id, '_hvnly_active_map_address_key', $found_base . '_address');
                update_post_meta($property_id, '_hvnly_active_map_latitude_key', $found_base . '_latitude');
                update_post_meta($property_id, '_hvnly_active_map_longitude_key', $found_base . '_longitude');
            }
            
            // if (defined('WP_DEBUG') && WP_DEBUG) {
            //     error_log("Havenlytics Map: Found coordinates via pattern search for property $property_id");
            // }
            return $result;
        }

        // ============================================================
        // METHOD 3: Check standard legacy meta keys (backward compatibility)
        // ============================================================
        $standard_lat = get_post_meta($property_id, '_hvnly_property_location_Latitude', true);
        $standard_lng = get_post_meta($property_id, '_hvnly_property_location_Longitude', true);
        
        if (empty($standard_lat)) {
            $standard_lat = get_post_meta($property_id, '_hvnly_property_latitude', true);
        }
        if (empty($standard_lng)) {
            $standard_lng = get_post_meta($property_id, '_hvnly_property_longitude', true);
        }
        
        if (!empty($standard_lat) && is_numeric($standard_lat) && !empty($standard_lng) && is_numeric($standard_lng)) {
            $result['lat'] = (float) $standard_lat;
            $result['lng'] = (float) $standard_lng;
            $result['address'] = get_post_meta($property_id, '_hvnly_property_map_location_address', true);
            $result['has_coordinates'] = true;
            
            // if (defined('WP_DEBUG') && WP_DEBUG) {
            //     error_log("Havenlytics Map: Found coordinates from standard keys for property $property_id");
            // }
            return $result;
        }

        // ============================================================
        // METHOD 4: Generic fallback - search for any meta keys containing lat/lng
        // ============================================================
        $found_lat = null;
        $found_lng = null;
        $found_address = null;
        
        foreach ($all_meta as $meta_key => $meta_value) {
            $key_lower = strtolower($meta_key);
            $value = is_array($meta_value) ? ($meta_value[0] ?? '') : $meta_value;
            
            if (strpos($key_lower, '_edit_') === 0 || strpos($key_lower, '_wp_') === 0) {
                continue;
            }
            
            if ((strpos($key_lower, 'lat') !== false || strpos($key_lower, 'latitude') !== false) && is_numeric($value)) {
                $found_lat = (float) $value;
            }
            if ((strpos($key_lower, 'lng') !== false || strpos($key_lower, 'lon') !== false || strpos($key_lower, 'longitude') !== false) && is_numeric($value)) {
                $found_lng = (float) $value;
            }
            if ((strpos($key_lower, 'address') !== false || strpos($key_lower, 'location') !== false) && !empty($value) && is_string($value)) {
                if (strlen($value) > 5) {
                    $found_address = $value;
                }
            }
        }
        
        if ($found_lat !== null && $found_lng !== null) {
            $result['lat'] = $found_lat;
            $result['lng'] = $found_lng;
            $result['address'] = $found_address;
            $result['has_coordinates'] = true;
            
            // if (defined('WP_DEBUG') && WP_DEBUG) {
            //     error_log("Havenlytics Map: Found coordinates from generic search for property $property_id");
            // }
        }

        return $result;
    }

    /**
     * Render map container via AJAX
     *
     * @return void
     */
    public function hvnly_render_map_container()
    {
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        
        if (!wp_verify_nonce($nonce, 'hvnly_ajax_request')) {
            wp_send_json_error('Security check failed');
        }
        
        $provider = isset($_POST['provider']) ? sanitize_text_field(wp_unslash($_POST['provider'])) : 'leaflet';
        
        ob_start();
        hvnly_get_template_part('map/map-container', null, array('provider' => $provider));
        $html = ob_get_clean();
        
        wp_send_json_success(array(
            'html' => $html,
            'provider' => $provider
        ));
    }

    /**
     * Search properties via AJAX with caching
     *
     * @return void
     */
    public function search_properties()
    {
        // Fixed nonce handling with proper unslashing
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        
        if (!wp_verify_nonce($nonce, 'hvnly_ajax_request')) {  
            wp_send_json_error('Security check failed');
        }

        try {
            $page = isset($_POST['page']) ? absint(wp_unslash($_POST['page'])) : 1;
            
            // Use custom properties per page setting instead of WordPress default
            $custom_per_page = function_exists('hvnly_get_properties_per_page') 
    ? hvnly_get_properties_per_page() 
    : get_option('posts_per_page', 12);

$per_page = isset($_POST['per_page']) ? absint(wp_unslash($_POST['per_page'])) : $custom_per_page;

            $is_top_search = isset($_POST['is_top_search']) && sanitize_text_field(wp_unslash($_POST['is_top_search'])) === 'true';

            // Sanitize POST data
            $post_data = $this->sanitize_post_data($_POST);

            // Build query args and check cache
            $args = $this->build_query_args($post_data, $page, $per_page);
            $cache_key = $this->generate_search_cache_key($args, $page, $per_page);

            // Try to get cached results
            $cached_results = $this->get_cached_search_results($cache_key);

            if (false !== $cached_results && !$is_top_search) {
                // Use cached results
                wp_send_json_success($cached_results);
            }

            // No cache found or top search - perform fresh query
            $properties_query = new \WP_Query($args);

            if (is_wp_error($properties_query->posts)) {
                wp_send_json_error('Query error occurred');
            }

            // Generate response data using templates
            $response_data = $this->generate_search_response($properties_query, $page, $per_page, $post_data, $is_top_search);

            // Cache the results (except for top search)
            if (!$is_top_search) {
                $this->set_cached_search_results($cache_key, $response_data);
            }

            wp_send_json_success($response_data);
        } catch (\Exception $e) {
            wp_send_json_error('An error occurred while loading properties. Please try again.');
        } catch (\Error $e) {
            wp_send_json_error('An error occurred while loading properties. Please try again.');
        }
    }

    /**
     * Sanitize POST data
     *
     * @param array $data Raw POST data
     * @return array
     */
    private function sanitize_post_data($data)
    {
        $sanitized = array();
        
        // Define allowed keys and their sanitization callbacks
        $allowed_keys = array(
            'nonce' => 'sanitize_text_field',
            'page' => 'absint',
            'per_page' => 'absint',
            'is_top_search' => 'sanitize_text_field',
            'address_keyword' => 'sanitize_text_field',
            'department' => 'sanitize_text_field',
            'min_price' => 'absint',
            'max_price' => 'absint',
            'bedrooms' => 'absint',
            'bathrooms' => 'absint',
            'reception_rooms' => 'absint',
            'garages' => 'absint',
            'orderby' => 'sanitize_text_field',
            'property_ids' => array($this, 'sanitize_array_field'),
            'property_type' => array($this, 'sanitize_array_field'),
            'hvnly_prop_depts' => array($this, 'sanitize_array_field'),
            'hvnly_prop_types' => array($this, 'sanitize_array_field'),
            'hvnly_prop_locations' => array($this, 'sanitize_array_field'),
            'in_tag' => array($this, 'sanitize_taxonomy_ids'),
            'in_badge' => array($this, 'sanitize_taxonomy_ids'),
            'in_status' => array($this, 'sanitize_taxonomy_ids'),
            'in_type' => array($this, 'sanitize_taxonomy_ids'),
            'in_location' => array($this, 'sanitize_taxonomy_ids'),
            'in_feature' => array($this, 'sanitize_taxonomy_ids'),
            'in_review' => array($this, 'sanitize_taxonomy_ids'),
            'amenities' => array($this, 'sanitize_array_field'),
            'hvnly_prop_features' => array($this, 'sanitize_array_field'),
            'hvnly_prop_reviews' => array($this, 'sanitize_array_field'),
            'hvnly_prop_tags' => array($this, 'sanitize_array_field'),
            'hvnly_prop_badges' => array($this, 'sanitize_array_field'),
            'hvnly_prop_status' => array($this, 'sanitize_array_field')
        );
        
        foreach ($allowed_keys as $key => $callback) {
            if (isset($data[$key])) {
                if (is_array($callback)) {
                    $sanitized[$key] = call_user_func($callback, $data[$key]);
                } elseif (is_callable($callback)) {
                    $sanitized[$key] = $callback(wp_unslash($data[$key]));
                } else {
                    $sanitized[$key] = $callback;
                }
            }
        }
        
        return $sanitized;
    }

    /**
     * Sanitize array field
     *
     * @param mixed $data
     * @return array
     */
    private function sanitize_array_field($data)
    {
        if (empty($data)) {
            return array();
        }
        
        if (!is_array($data)) {
            $data = explode(',', $data);
        }
        
        return array_map('sanitize_text_field', array_filter($data));
    }

    /**
     * Sanitize taxonomy IDs
     *
     * @param mixed $data
     * @return array
     */
    private function sanitize_taxonomy_ids($data)
    {
        if (empty($data)) {
            return array();
        }
        
        // Replace %2C with comma if present
        $data = str_replace('%2C', ',', $data);
        
        if (!is_array($data)) {
            $data = explode(',', $data);
        }
        
        return array_map('absint', array_filter($data));
    }

    /**
     * Generate cache key for search results
     *
     * @param array $args
     * @param int   $page
     * @param int   $per_page
     * @return string
     */
    private function generate_search_cache_key($args, $page, $per_page)
    {
        $key_data = array(
            'args' => $args,
            'page' => $page,
            'per_page' => $per_page,
            'lang' => function_exists('pll_current_language') ? pll_current_language() : get_locale()
        );

        $cache_key = 'hvnly_search_' . md5(serialize($key_data));

        return apply_filters('hvnly_search_cache_key', $cache_key, $key_data);
    }

    /**
     * Get cached search results
     *
     * @param string $cache_key
     * @return mixed
     */
    private function get_cached_search_results($cache_key)
    {
        $cached = get_transient($cache_key);

        return apply_filters('hvnly_get_cached_search_results', $cached, $cache_key);
    }

    /**
     * Set cached search results
     *
     * @param string $cache_key Cache key
     * @param mixed  $data      Data to cache
     */
    private function set_cached_search_results($cache_key, $data)
    {
        $duration = apply_filters('hvnly_search_cache_duration', $this->search_cache_duration);
        set_transient($cache_key, $data, $duration);

        do_action('hvnly_set_cached_search_results', $cache_key, $data, $duration);
    }

    /**
     * Generate search response data using templates
     *
     * @param \WP_Query $query       Properties query
     * @param int      $page        Current page
     * @param int      $per_page    Posts per page
     * @param array    $filters     Current filters
     * @param bool     $is_top_search Whether it's a top search
     * @return array
     */
    private function generate_search_response($query, $page, $per_page, $filters, $is_top_search = false)
    {
        global $wp_query;

        // Set up global query for template compatibility
        $original_query = $wp_query;
        $wp_query = $query;

        // Use template for property grid
        ob_start();
        $this->load_property_grid_template($query, $is_top_search);
        $html = ob_get_clean();

        // Restore original query
        $wp_query = $original_query;

        if ($is_top_search) {
            return array(
                'html' => $html,
                'found_posts' => $query->found_posts,
            );
        }

        // Generate pagination using template
        ob_start();
        $this->generate_pagination_template($query, $page);
        $pagination_html = ob_get_clean();

        // Generate results count using template
        ob_start();
        $this->generate_results_count_template($query, $page, $per_page, $filters);
        $results_count_html = ob_get_clean();

        // Calculate start and end for display
        $start = (($page - 1) * $per_page) + 1;
        $end = min($page * $per_page, $query->found_posts);
        $loaded_count = min($page * $per_page, $query->found_posts);

        return array(
            'html' => $html,
            'pagination_html' => $pagination_html,
            'results_count_html' => $results_count_html,
            'found_posts' => $query->found_posts,
            'max_pages' => $query->max_num_pages,
            'current_page' => $page,
            'post_count' => $query->post_count,
            'start' => $start,
            'end' => $end,
            'loaded_count' => $loaded_count,
        );
    }

    /**
     * Load property grid using template
     *
     * @param \WP_Query $query Properties query
     * @param bool $is_top_search Whether it's a top search
     * @return void
     */
    private function load_property_grid_template($query, $is_top_search = false)
    {
        if ($query->have_posts()) {
            if ($is_top_search) {
                // Use simple card template for top search
                while ($query->have_posts()) {
                    $query->the_post();
                    hvnly_get_template_part('content', 'property');
                }
            } else {
                // Use regular card template for main search
                while ($query->have_posts()) {
                    $query->the_post();
                    hvnly_get_template_part('content', 'property');
                }
            }
            wp_reset_postdata();
        } else {
            // Use no results template
            hvnly_get_template_part('content', 'none');
        }
    }

    /**
     * Generate pagination using existing template
     *
     * @param \WP_Query $query Properties query
     * @param int $current_page Current page number
     * @return void
     */
    private function generate_pagination_template($query, $current_page)
    {
        global $wp_query;

        // Temporarily set the global query for template compatibility
        $original_query = $wp_query;
        $wp_query = $query;

        // Include your existing pagination template
        $template_path = locate_template('havenlytics/search/pagination.php');
        if (!$template_path) {
            $template_path = HVNLYNAB_PATH . 'templates/search/pagination.php';
        }

        if (file_exists($template_path)) {
            include $template_path;
        }

        // Restore original query
        $wp_query = $original_query;
    }

    /**
     * Generate results count using existing template
     *
     * @param \WP_Query $query Properties query
     * @param int $current_page Current page number
     * @param int $per_page Posts per page
     * @param array $filters Current filters
     * @return void
     */
    private function generate_results_count_template($query, $current_page, $per_page, $filters)
    {
        global $wp_query;

        // Temporarily set the global query for template compatibility
        $original_query = $wp_query;
        $wp_query = $query;

        // Prepare variables for template
        $total_properties = $query->found_posts;
        $start = (($current_page - 1) * $per_page) + 1;
        $end = min($current_page * $per_page, $total_properties);

        // Get current department name if filtered
        $hvnly_current_filters = array();
        if (!empty($filters['department'])) {
            $department_term = get_term_by('slug', sanitize_text_field($filters['department']), 'hvnly_prop_depts');
            if ($department_term && !is_wp_error($department_term)) {
                $hvnly_current_filters['property_type'] = array($department_term->name);
            }
        }

        // Get location names if filtered
        if (!empty($filters['hvnly_prop_locations'])) {
            $location_names = array();
            foreach ((array)$filters['hvnly_prop_locations'] as $location_slug) {
                $location_term = get_term_by('slug', $location_slug, 'hvnly_prop_locations');
                if ($location_term) {
                    $location_names[] = $location_term->name;
                }
            }
            if (!empty($location_names)) {
                $hvnly_current_filters['location'] = $location_names;
            }
        }

        // Include your existing results count template
        $template_path = locate_template('havenlytics/search/result-count.php');
        if (!$template_path) {
            $template_path = HVNLYNAB_PATH . 'templates/search/result-count.php';
        }

        if (file_exists($template_path)) {
            // Pass variables to template
            $args = array(
                'total_properties' => $total_properties,
                'start' => $start,
                'end' => $end,
                'hvnly_current_filters' => $hvnly_current_filters
            );

            extract($args);
            include $template_path;
        }

        // Restore original query
        $wp_query = $original_query;
    }

    /**
     * Clear sidebar terms cache when terms are modified
     *
     * @param int    $term_id Term ID
     * @param int    $tt_id   Term taxonomy ID
     * @param string $taxonomy Taxonomy name
     */
    public function clear_sidebar_terms_cache($term_id, $tt_id = 0, $taxonomy = '')
    {
        // Handle different hook signatures
        if (func_num_args() === 1) {
            // For delete_term hook (3 params)
            $term_id = func_get_arg(0);
            $taxonomy = func_get_arg(2);
        } elseif (func_num_args() === 2) {
            // For created_term and edited_term hooks
            $term_id = func_get_arg(0);
            $taxonomy = func_get_arg(1);
        }
        
        // Early validation to prevent unnecessary processing
        if (empty($term_id) || !is_numeric($term_id)) {
            return;
        }

        $term = get_term($term_id);
        
        // Comprehensive validation for term existence and validity
        if (!$term || is_wp_error($term) || !isset($term->taxonomy)) {
            return;
        }

        // Only proceed if we have a valid term with taxonomy
        try {
            if (class_exists('\HvnlyNab\Frontend\ViewModels\SidebarSearchFilters')) {
                $sidebar_filters = new \HvnlyNab\Frontend\ViewModels\SidebarSearchFilters();
                $sidebar_filters->clear_terms_cache($term->taxonomy);
            }
            
            do_action('hvnly_cleared_sidebar_terms_cache', $term_id, $term->taxonomy);
        } catch (\Exception $e) {
            // Silent fail in production
        }
    }

    
    /**
     * Clear cache when property is saved or deleted
     *
     * @param int $post_id
     */
    public function clear_property_cache($post_id)
    {
        if (get_post_type($post_id) !== 'hvnly_property') {
            return;
        }

        $this->clear_all_search_transients();
        do_action('hvnly_cleared_property_cache', $post_id);
    }

    /**
     * Clear cache when terms are modified
     *
     * @param int    $term_id Term ID
     * @param int    $tt_id   Term taxonomy ID
     * @param string $taxonomy Taxonomy name
     */
    public function clear_terms_cache($term_id, $tt_id = 0, $taxonomy = '')
    {
        // Handle different hook signatures
        if (func_num_args() === 1) {
            // For delete_term hook (3 params)
            $term_id = func_get_arg(0);
            $taxonomy = func_get_arg(2);
        } elseif (func_num_args() === 2) {
            // For created_term and edited_term hooks
            $term_id = func_get_arg(0);
            $taxonomy = func_get_arg(1);
        }
        
        // Early validation to prevent unnecessary processing
        if (empty($term_id) || !is_numeric($term_id)) {
            $this->clear_all_search_transients();
            do_action('hvnly_cleared_terms_cache', $term_id);
            return;
        }

        $term = get_term($term_id);
        
        // Comprehensive validation for term existence and validity
        if (!$term || is_wp_error($term) || !isset($term->taxonomy)) {
            $this->clear_all_search_transients();
            do_action('hvnly_cleared_terms_cache', $term_id);
            return;
        }

        // Only proceed if we have a valid term with taxonomy
        try {
            if (class_exists('\HvnlyNab\Frontend\ViewModels\SearchFilters')) {
                $search_filters = new \HvnlyNab\Frontend\ViewModels\SearchFilters();
                $search_filters->clear_terms_cache($term->taxonomy);
            }
        } catch (\Exception $e) {
            // Silent fail in production
        }

        $this->clear_all_search_transients();
        do_action('hvnly_cleared_terms_cache', $term_id);
    }

    /**
     * Clear all search-related transients
     *
     * Uses direct database query with proper suppression comments since
     * pattern-based transient deletion cannot be accomplished with standard
     * WordPress API. This is the most efficient method for clearing
     * multiple transients matching a pattern.
     */
    private function clear_all_search_transients()
    {
        global $wpdb;
        
        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        // Direct query required for pattern-based transient deletion
        $transient_keys = $wpdb->get_col(
            $wpdb->prepare(
                "SELECT option_name 
                FROM {$wpdb->options} 
                WHERE option_name LIKE %s 
                OR option_name LIKE %s",
                '_transient_hvnly_search_%',
                '_transient_timeout_hvnly_search_%'
            )
        );
        // phpcs:enable
        
        if (empty($transient_keys)) {
            do_action('hvnly_cleared_all_search_transients');
            return;
        }
        
        // Batch delete transients for better performance
        $deleted_count = 0;
        foreach ($transient_keys as $key) {
            // Extract transient name using regex for reliability
            if (preg_match('/^_transient_(?:timeout_)?(.+)$/', $key, $matches)) {
                $transient_name = $matches[1];
                
                // Use WordPress API to ensure proper cache invalidation
                if (delete_transient($transient_name)) {
                    $deleted_count++;
                }
            }
        }
        
        // Log deletion for debugging if needed (only in debug mode)
        // if (defined('WP_DEBUG') && WP_DEBUG && $deleted_count > 0) {
        //     error_log(sprintf(
        //         'Havenlytics: Cleared %d search transients',
        //         $deleted_count
        //     ));
        // }
        
        do_action('hvnly_cleared_all_search_transients');
    }

    /**
     * Build query args from request data
     *
     * @param array $data Request data
     * @param int   $page Current page
     * @param int   $per_page Posts per page
     * @return array Query args
     */
    private function build_query_args($data, $page = 1, $per_page = 12)
    {
        $args = array(
            'post_type' => 'hvnly_property',
            'post_status' => 'publish',
            'paged' => $page,
            'posts_per_page' => $per_page,
        );
        
        // Property ID filter
        if (!empty($data['property_ids'])) {
            $property_ids = is_array($data['property_ids']) ? $data['property_ids'] : array($data['property_ids']);
            $clean_property_ids = array_map('sanitize_text_field', array_filter($property_ids));
            
            if (!empty($clean_property_ids)) {
                // Create a meta query to search for property IDs
                $meta_query = array(
                    'relation' => 'OR'
                );
                
                foreach ($clean_property_ids as $property_id) {
                    $meta_query[] = array(
                        'key' => '_hvnly_unique_property_id',
                        'value' => $property_id,
                        'compare' => 'LIKE'
                    );
                }
                
                $args['meta_query'] = $meta_query;
            }
        }
        
        // Keyword search
        if (!empty($data['address_keyword'])) {
            $args['s'] = sanitize_text_field($data['address_keyword']);
        }

        // Tax queries
        $tax_query = array();

        // Handle department from tabs
        if (!empty($data['department'])) {
            $department = sanitize_text_field($data['department']);
            if ($department !== '') {
                $tax_query[] = array(
                    'taxonomy' => 'hvnly_prop_depts',
                    'field' => 'slug',
                    'terms' => $department,
                );
            }
        }

        // Handle property_type from top search
        if (!empty($data['property_type'])) {
            $property_types = is_array($data['property_type']) ? $data['property_type'] : array($data['property_type']);
            $clean_property_types = array_map('sanitize_text_field', $property_types);

            $tax_query[] = array(
                'taxonomy' => 'hvnly_prop_types',
                'field' => 'slug',
                'terms' => $clean_property_types,
            );
        }

        // Handle hvnly_prop_depts from top search
        if (!empty($data['hvnly_prop_depts'])) {
            $property_types = is_array($data['hvnly_prop_depts']) ? $data['hvnly_prop_depts'] : array($data['hvnly_prop_depts']);
            $clean_property_types = array_map('sanitize_text_field', $property_types);

            $tax_query[] = array(
                'taxonomy' => 'hvnly_prop_depts',
                'field' => 'slug',
                'terms' => $clean_property_types,
            );
        }

        // Handle hvnly_prop_types from top search
        if (!empty($data['hvnly_prop_types'])) {
            $types = is_array($data['hvnly_prop_types']) ? $data['hvnly_prop_types'] : array($data['hvnly_prop_types']);
            $clean_types = array_map('sanitize_text_field', $types);

            $tax_query[] = array(
                'taxonomy' => 'hvnly_prop_types',
                'field' => 'slug',
                'terms' => $clean_types,
            );
        }

        // Handle hvnly_prop_locations from top search
        if (!empty($data['hvnly_prop_locations'])) {
            $locations = is_array($data['hvnly_prop_locations']) ? $data['hvnly_prop_locations'] : array($data['hvnly_prop_locations']);
            $clean_locations = array_map('sanitize_text_field', $locations);

            $tax_query[] = array(
                'taxonomy' => 'hvnly_prop_locations',
                'field' => 'slug',
                'terms' => $clean_locations,
            );
        }

        // Handle taxonomy parameters with term IDs
        $in_taxonomy_params = array(
            'in_tag' => 'hvnly_prop_tags',
            'in_badge' => 'hvnly_prop_badges',
            'in_status' => 'hvnly_prop_status',
            'in_type' => 'hvnly_prop_types',
            'in_location' => 'hvnly_prop_locations',
            'in_feature' => 'hvnly_prop_features',
            'in_review' => 'hvnly_prop_reviews'
        );

        foreach ($in_taxonomy_params as $param => $taxonomy) {
            if (!empty($data[$param])) {
                $param_value = $data[$param];
                $term_ids = is_array($param_value) ? $param_value : array_map('absint', explode(',', $param_value));
                $term_ids = array_filter($term_ids);

                if (!empty($term_ids)) {
                    $tax_query[] = array(
                        'taxonomy' => $taxonomy,
                        'field' => 'term_id',
                        'terms' => $term_ids,
                    );
                }
            }
        }

        // Handle array inputs for taxonomies
        $taxonomies = array(
            'hvnly_prop_depts' => 'hvnly_prop_depts',
            'hvnly_prop_locations' => 'hvnly_prop_locations',
            'hvnly_prop_features' => 'hvnly_prop_features',
            'hvnly_prop_reviews' => 'hvnly_prop_reviews',
            'hvnly_prop_tags' => 'hvnly_prop_tags',
            'hvnly_prop_badges' => 'hvnly_prop_badges',
            'hvnly_prop_status' => 'hvnly_prop_status',
            'amenities' => 'amenities'
        );

        foreach ($taxonomies as $field => $taxonomy) {
            if (!empty($data[$field])) {
                if (is_array($data[$field])) {
                    $terms = $data[$field];
                } else {
                    $terms = explode(',', $data[$field]);
                }

                $clean_terms = array_map('sanitize_text_field', array_filter($terms));

                if (!empty($clean_terms)) {
                    $tax_query[] = array(
                        'taxonomy' => $taxonomy,
                        'field' => 'slug',
                        'terms' => $clean_terms,
                    );
                }
            }
        }

        // Set up tax query with proper relationship
        if (!empty($tax_query)) {
            $grouped_tax_query = array();
            foreach ($tax_query as $tax_item) {
                $taxonomy = $tax_item['taxonomy'];
                if (!isset($grouped_tax_query[$taxonomy])) {
                    $grouped_tax_query[$taxonomy] = $tax_item;
                } else {
                    if (is_array($grouped_tax_query[$taxonomy]['terms'])) {
                        $grouped_tax_query[$taxonomy]['terms'] = array_merge(
                            $grouped_tax_query[$taxonomy]['terms'],
                            $tax_item['terms']
                        );
                    }
                }
            }

            $tax_query = array_values($grouped_tax_query);

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

            $args['tax_query'] = $tax_query;
        }

        // Meta queries
        $meta_query = array();

        // Price range
        $min_price = !empty($data['min_price']) ? absint($data['min_price']) : 0;
        $max_price = !empty($data['max_price']) ? absint($data['max_price']) : 0;

        if ($min_price > 0 || $max_price > 0) {
            $price_query = array();

            if ($min_price > 0) {
                $price_query[] = array(
                    'key' => '_hvnly_property_price',
                    'value' => $min_price,
                    'type' => 'NUMERIC',
                    'compare' => '>=',
                );
            }

            if ($max_price > 0) {
                $price_query[] = array(
                    'key' => '_hvnly_property_price',
                    'value' => $max_price,
                    'type' => 'NUMERIC',
                    'compare' => '<=',
                );
            }

            if (count($price_query) > 1) {
                $price_query['relation'] = 'AND';
                $meta_query[] = $price_query;
            } elseif (!empty($price_query)) {
                $meta_query[] = $price_query[0];
            }
        }

        // Bedrooms
        if (!empty($data['bedrooms'])) {
            $meta_query[] = array(
                'key' => '_hvnly_property_bedrooms',
                'value' => absint($data['bedrooms']),
                'type' => 'NUMERIC',
                'compare' => '>=',
            );
        }

        // Bathrooms
        if (!empty($data['bathrooms'])) {
            $meta_query[] = array(
                'key' => '_hvnly_property_bathrooms',
                'value' => absint($data['bathrooms']),
                'type' => 'NUMERIC',
                'compare' => '>=',
            );
        }

        // Reception Rooms
        if (!empty($data['reception_rooms'])) {
            $meta_query[] = array(
                'key' => '_hvnly_property_reception_rooms',
                'value' => absint($data['reception_rooms']),
                'type' => 'NUMERIC',
                'compare' => '>=',
            );
        }

        // Garages
        if (!empty($data['garages'])) {
            $meta_query[] = array(
                'key' => '_hvnly_property_garage_sqft',
                'value' => absint($data['garages']),
                'type' => 'NUMERIC',
                'compare' => '>=',
            );
        }

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

   
        // Order by - Updated to handle more sort options
        if (!empty($data['orderby'])) {
            $orderby = sanitize_text_field($data['orderby']);
            
            switch ($orderby) {
                case 'a-to-z-title':
                    $args['orderby'] = 'title';
                    $args['order'] = 'ASC';
                    break;
                case 'z-to-a-title':
                    $args['orderby'] = 'title';
                    $args['order'] = 'DESC';
                    break;
                case 'date':
                    $args['orderby'] = 'date';
                    $args['order'] = 'DESC';
                    break;
                case 'date_oldest':
                    $args['orderby'] = 'date';
                    $args['order'] = 'ASC';
                    break;
                case 'price_low':
                    $args['meta_key'] = '_hvnly_property_price';
                    $args['orderby'] = 'meta_value_num';
                    $args['order'] = 'ASC';
                    break;
                case 'price_high':
                    $args['meta_key'] = '_hvnly_property_price';
                    $args['orderby'] = 'meta_value_num';
                    $args['order'] = 'DESC';
                    break;
                case 'area':
                    $args['meta_key'] = '_hvnly_property_sqft';
                    $args['orderby'] = 'meta_value_num';
                    $args['order'] = 'DESC';
                    break;
                case 'bedrooms':
                    $args['meta_key'] = '_hvnly_property_bedrooms';
                    $args['orderby'] = 'meta_value_num';
                    $args['order'] = 'DESC';
                    break;
                case 'views':
                    $args['meta_key'] = '_hvnly_property_views';
                    $args['orderby'] = 'meta_value_num';
                    $args['order'] = 'DESC';
                    break;
                case 'rating':
                    $args['meta_key'] = '_hvnly_property_rating';
                    $args['orderby'] = 'meta_value_num';
                    $args['order'] = 'DESC';
                    break;
                case 'rand_property':
                    $args['orderby'] = 'rand';
                    break;
                default:
                    $args['orderby'] = 'date';
                    $args['order'] = 'DESC';
                    break;
            }
        } else {
            // No orderby specified - use default from settings
            $default_sort = function_exists('hvnly_get_default_sort_option') ? hvnly_get_default_sort_option() : 'date';
            
            switch ($default_sort) {
                case 'a-to-z-title':
                    $args['orderby'] = 'title';
                    $args['order'] = 'ASC';
                    break;
                case 'z-to-a-title':
                    $args['orderby'] = 'title';
                    $args['order'] = 'DESC';
                    break;
                case 'date_oldest':
                    $args['orderby'] = 'date';
                    $args['order'] = 'ASC';
                    break;
                case 'price_low':
                    $args['meta_key'] = '_hvnly_property_price';
                    $args['orderby'] = 'meta_value_num';
                    $args['order'] = 'ASC';
                    break;
                case 'price_high':
                    $args['meta_key'] = '_hvnly_property_price';
                    $args['orderby'] = 'meta_value_num';
                    $args['order'] = 'DESC';
                    break;
                case 'area':
                    $args['meta_key'] = '_hvnly_property_sqft';
                    $args['orderby'] = 'meta_value_num';
                    $args['order'] = 'DESC';
                    break;
                case 'bedrooms':
                    $args['meta_key'] = '_hvnly_property_bedrooms';
                    $args['orderby'] = 'meta_value_num';
                    $args['order'] = 'DESC';
                    break;
                case 'views':
                    $args['meta_key'] = '_hvnly_property_views';
                    $args['orderby'] = 'meta_value_num';
                    $args['order'] = 'DESC';
                    break;
                case 'rating':
                    $args['meta_key'] = '_hvnly_property_rating';
                    $args['orderby'] = 'meta_value_num';
                    $args['order'] = 'DESC';
                    break;
                case 'rand_property':
                    $args['orderby'] = 'rand';
                    break;
                case 'date':
                default:
                    $args['orderby'] = 'date';
                    $args['order'] = 'DESC';
                    break;
            }
        }

        return apply_filters('hvnly_property_query_args', $args, $data);
    }

    /**
     * Get properties for map view with Property Builder support
     *
     * @return void
     */
    public function hvnly_get_properties_for_map()
    {
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        
        if (!wp_verify_nonce($nonce, 'hvnly_ajax_request')) {  
            wp_send_json_error('Security check failed');
        }

        try {
            $page = isset($_POST['page']) ? absint(wp_unslash($_POST['page'])) : 1;
            
            $custom_per_page = function_exists('hvnly_get_properties_per_page') 
                ? hvnly_get_properties_per_page() 
                : get_option('posts_per_page', 12);
            
            $per_page = isset($_POST['per_page']) ? absint(wp_unslash($_POST['per_page'])) : $custom_per_page;

            $post_data = $this->sanitize_post_data($_POST);
            $args = $this->build_query_args($post_data, $page, $per_page);
            $cache_key = $this->generate_search_cache_key($args, $page, $per_page) . '_map';

            $cached_results = $this->get_cached_search_results($cache_key);

            if (false !== $cached_results) {
                wp_send_json_success($cached_results);
            }

            $properties_query = new \WP_Query($args);
            $properties = array();

            if ($properties_query->have_posts()) {
                while ($properties_query->have_posts()) {
                    $properties_query->the_post();
                    $post_id = get_the_ID();

                    $map_data = $this->get_property_map_data($post_id);
                    
                    $price = get_post_meta($post_id, '_hvnly_property_price', true);
                    $bedrooms = get_post_meta($post_id, '_hvnly_property_bedrooms', true);
                    $bathrooms = get_post_meta($post_id, '_hvnly_property_bathrooms', true);

                    $formatted_price = '';
                    if (!empty($price)) {
                        if (is_numeric($price)) {
                            $formatted_price = '$' . number_format((float)$price);
                        } elseif (is_string($price)) {
                            $decoded = json_decode($price, true);
                            if (is_array($decoded) && isset($decoded['__type']) && $decoded['__type'] === 'custom_label') {
                                $formatted_price = isset($decoded['label']) ? $decoded['label'] : $price;
                            } else {
                                $clean_price = preg_replace('/[^0-9.]/', '', $price);
                                if (is_numeric($clean_price)) {
                                    $formatted_price = '$' . number_format((float)$clean_price);
                                } else {
                                    $formatted_price = $price;
                                }
                            }
                        }
                    }

                    $property_data = array(
                        'id' => $post_id,
                        'title' => get_the_title(),
                        'lat' => $map_data['lat'],
                        'lng' => $map_data['lng'],
                        'address' => $map_data['address'],
                        'price' => $formatted_price,
                        'bedrooms' => $bedrooms,
                        'bathrooms' => $bathrooms,
                        'thumbnail' => get_the_post_thumbnail_url($post_id, 'medium'),
                        'link' => get_permalink(),
                        'has_coordinates' => $map_data['has_coordinates'],
                        'popup_html' => function_exists('hvnly_get_property_popup') ? hvnly_get_property_popup(array(
                            'title' => get_the_title(),
                            'price' => $formatted_price,
                            'address' => $map_data['address'],
                            'bedrooms' => $bedrooms,
                            'bathrooms' => $bathrooms,
                            'thumbnail' => get_the_post_thumbnail_url($post_id, 'medium'),
                            'link' => get_permalink()
                        )) : ''
                    );

                    $properties[] = $property_data;
                }
                wp_reset_postdata();
            }

            $valid_properties = array_filter($properties, function ($property) {
                return $property['has_coordinates'];
            });

            $start = (($page - 1) * $per_page) + 1;
            $end = min($page * $per_page, $properties_query->found_posts);

            ob_start();
            $this->generate_pagination_template($properties_query, $page);
            $pagination_html = ob_get_clean();

            ob_start();
            $this->generate_results_count_template($properties_query, $page, $per_page, $post_data);
            $results_count_html = ob_get_clean();

            $response_data = array(
                'properties' => array_values($valid_properties),
                'count' => count($valid_properties),
                'found_posts' => $properties_query->found_posts,
                'max_pages' => $properties_query->max_num_pages,
                'current_page' => $page,
                'start' => $start,
                'end' => $end,
                'pagination_html' => $pagination_html,
                'results_count_html' => $results_count_html,
                'loaded_count' => min($page * $per_page, $properties_query->found_posts),
            );

            $this->set_cached_search_results($cache_key, $response_data);
            wp_send_json_success($response_data);
            
        } catch (\Exception $e) {
            wp_send_json_error('An error occurred while loading map properties.');
        } catch (\Error $e) {
            wp_send_json_error('An error occurred while loading map properties.');
        }
    }

    /**
     * Load more properties
     *
     * @return void
     */
    public function load_more_properties()
    {
        $this->search_properties();
    }

    /**
     * Get filter options via AJAX
     *
     * @return void
     */
    public function get_filter_options()
    {
        // Fixed nonce handling with proper unslashing
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        
        if (!wp_verify_nonce($nonce, 'hvnly_ajax_request')) {
            wp_send_json_error('Security check failed');
        }

        // Fixed taxonomy parameter handling
        $taxonomy = isset($_POST['taxonomy']) ? sanitize_text_field(wp_unslash($_POST['taxonomy'])) : '';

        if (empty($taxonomy)) {
            wp_send_json_error(__('Taxonomy not specified', 'havenlytics'));
        }

        $terms = get_terms(array(
            'taxonomy' => $taxonomy,
            'hide_empty' => true,
        ));

        if (is_wp_error($terms)) {
            wp_send_json_error(__('Error fetching terms', 'havenlytics'));
        }

        $options = array();
        foreach ($terms as $term) {
            $options[] = array(
                'value' => $term->slug,
                'label' => $term->name,
                'count' => $term->count,
            );
        }

        wp_send_json_success($options);
    }
}