<?php
/**
 * Training Map Data Provider
 *
 * Provides marker data for trainers and venues on the Find Training map.
 *
 * @package HVAC_Community_Events
 * @since 2.2.0
 */

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

/**
 * Class HVAC_Training_Map_Data
 *
 * Data provider for the Find Training Google Maps integration.
 * Queries trainers and venues with coordinates for map display.
 */
class HVAC_Training_Map_Data {

    /**
     * Singleton instance
     *
     * @var HVAC_Training_Map_Data|null
     */
    private static ?self $instance = null;

    /**
     * Cache group for queries
     *
     * @var string
     */
    private string $cache_group = 'hvac_training_map';

    /**
     * Cache expiration (1 hour)
     *
     * @var int
     */
    private int $cache_expiration = 3600;

    /**
     * Get singleton instance
     *
     * @return HVAC_Training_Map_Data
     */
    public static function get_instance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor
     */
    private function __construct() {
        // Clear cache when profiles, venues, or events are updated
        add_action('save_post_trainer_profile', [$this, 'clear_trainer_cache']);
        add_action('save_post_tribe_venue', [$this, 'clear_venue_cache']);
        add_action('save_post_tribe_events', [$this, 'clear_event_cache']);
    }

    /**
     * Get trainer markers for map
     *
     * @param array $filters Optional filters
     * @return array Trainer markers data
     */
    public function get_trainer_markers(array $filters = []): array {
        // Generate cache key based on filters
        $cache_key = 'trainers_' . md5(serialize($filters));
        $cached = wp_cache_get($cache_key, $this->cache_group);

        if ($cached !== false && empty($filters)) {
            return $cached;
        }

        // Get approved user IDs
        $approved_user_ids = $this->get_approved_user_ids();

        if (empty($approved_user_ids)) {
            return [];
        }

        // Build query args
        $query_args = [
            'post_type' => 'trainer_profile',
            'posts_per_page' => -1,
            'post_status' => 'publish',
            'meta_query' => [
                'relation' => 'AND',
                [
                    'key' => 'is_public_profile',
                    'value' => '1',
                    'compare' => '='
                ],
                [
                    'key' => 'user_id',
                    'value' => $approved_user_ids,
                    'compare' => 'IN'
                ],
                [
                    'key' => 'latitude',
                    'compare' => 'EXISTS'
                ],
                [
                    'key' => 'longitude',
                    'compare' => 'EXISTS'
                ],
                [
                    'key' => 'latitude',
                    'value' => '',
                    'compare' => '!='
                ],
                [
                    'key' => 'longitude',
                    'value' => '',
                    'compare' => '!='
                ]
            ]
        ];

        // Add state filter
        if (!empty($filters['state'])) {
            $query_args['meta_query'][] = [
                'key' => 'trainer_state',
                'value' => sanitize_text_field($filters['state']),
                'compare' => '='
            ];
        }

        // Add search filter
        if (!empty($filters['search'])) {
            $search = sanitize_text_field($filters['search']);
            $query_args['meta_query'][] = [
                'relation' => 'OR',
                [
                    'key' => 'trainer_display_name',
                    'value' => $search,
                    'compare' => 'LIKE'
                ],
                [
                    'key' => 'trainer_city',
                    'value' => $search,
                    'compare' => 'LIKE'
                ],
                [
                    'key' => 'company_name',
                    'value' => $search,
                    'compare' => 'LIKE'
                ]
            ];
        }

        $query = new WP_Query($query_args);
        $markers = [];

        if ($query->have_posts()) {
            while ($query->have_posts()) {
                $query->the_post();
                $profile_id = get_the_ID();
                $marker = $this->format_trainer_marker($profile_id);

                // Apply certification filter
                if (!empty($filters['certification'])) {
                    $cert_match = false;
                    foreach ($marker['certifications'] as $cert) {
                        if (stripos($cert, $filters['certification']) !== false) {
                            $cert_match = true;
                            break;
                        }
                    }
                    if (!$cert_match) {
                        continue;
                    }
                }

                // Apply proximity filter
                if (!empty($filters['lat']) && !empty($filters['lng']) && !empty($filters['radius'])) {
                    $distance = $this->calculate_distance(
                        $filters['lat'],
                        $filters['lng'],
                        $marker['lat'],
                        $marker['lng']
                    );
                    if ($distance > $filters['radius']) {
                        continue;
                    }
                    $marker['distance'] = round($distance, 1);
                }

                $markers[] = $marker;
            }
        }

        wp_reset_postdata();

        // Sort by distance if proximity search
        if (!empty($filters['lat']) && !empty($filters['lng'])) {
            usort($markers, function($a, $b) {
                return ($a['distance'] ?? 0) <=> ($b['distance'] ?? 0);
            });
        }

        // Cache if no filters
        if (empty($filters)) {
            wp_cache_set($cache_key, $markers, $this->cache_group, $this->cache_expiration);
        }

        return $markers;
    }

    /**
     * Get venue markers for map
     *
     * Filters to show only measureQuick Approved Training Labs.
     *
     * @param array $filters Optional filters
     * @return array Venue markers data
     */
    public function get_venue_markers(array $filters = []): array {
        // Check if TEC is active
        if (!function_exists('tribe_get_venue')) {
            return [];
        }

        // Generate cache key
        $cache_key = 'venues_approved_labs_' . md5(serialize($filters));
        $cached = wp_cache_get($cache_key, $this->cache_group);

        if ($cached !== false && empty($filters)) {
            return $cached;
        }

        // Build query args - filter for approved training labs only
        $query_args = [
            'post_type' => 'tribe_venue',
            'posts_per_page' => -1,
            'post_status' => 'publish',
            // Only show venues tagged as measureQuick Approved Training Labs
            'tax_query' => [
                [
                    'taxonomy' => 'venue_type',
                    'field' => 'slug',
                    'terms' => 'mq-approved-lab',
                ],
            ],
            'meta_query' => [
                'relation' => 'AND',
                [
                    'relation' => 'OR',
                    // Check for our custom venue coordinates
                    [
                        'key' => 'venue_latitude',
                        'compare' => 'EXISTS'
                    ],
                    // Also check TEC's built-in coordinates
                    [
                        'key' => '_VenueLat',
                        'compare' => 'EXISTS'
                    ]
                ]
            ]
        ];

        // Add state filter
        if (!empty($filters['state'])) {
            $query_args['meta_query'][] = [
                'relation' => 'OR',
                [
                    'key' => '_VenueStateProvince',
                    'value' => sanitize_text_field($filters['state']),
                    'compare' => '='
                ],
                [
                    'key' => '_VenueState',
                    'value' => sanitize_text_field($filters['state']),
                    'compare' => '='
                ]
            ];
        }

        // Add search filter
        if (!empty($filters['search'])) {
            $query_args['s'] = sanitize_text_field($filters['search']);
        }

        $query = new WP_Query($query_args);
        $markers = [];

        if ($query->have_posts()) {
            while ($query->have_posts()) {
                $query->the_post();
                $venue_id = get_the_ID();
                $marker = $this->format_venue_marker($venue_id);

                // Skip venues without valid coordinates
                if (empty($marker['lat']) || empty($marker['lng'])) {
                    continue;
                }

                // Apply proximity filter
                if (!empty($filters['lat']) && !empty($filters['lng']) && !empty($filters['radius'])) {
                    $distance = $this->calculate_distance(
                        $filters['lat'],
                        $filters['lng'],
                        $marker['lat'],
                        $marker['lng']
                    );
                    if ($distance > $filters['radius']) {
                        continue;
                    }
                    $marker['distance'] = round($distance, 1);
                }

                $markers[] = $marker;
            }
        }

        wp_reset_postdata();

        // Sort by distance if proximity search
        if (!empty($filters['lat']) && !empty($filters['lng'])) {
            usort($markers, function($a, $b) {
                return ($a['distance'] ?? 0) <=> ($b['distance'] ?? 0);
            });
        }

        // Cache if no filters
        if (empty($filters)) {
            wp_cache_set($cache_key, $markers, $this->cache_group, $this->cache_expiration);
        }

        return $markers;
    }

    /**
     * Format trainer data for map marker
     *
     * @param int $profile_id Trainer profile post ID
     * @return array Formatted marker data
     */
    private function format_trainer_marker(int $profile_id): array {
        $user_id = get_post_meta($profile_id, 'user_id', true);
        $lat = get_post_meta($profile_id, 'latitude', true);
        $lng = get_post_meta($profile_id, 'longitude', true);

        // Get certifications
        $certifications = $this->get_trainer_certifications($profile_id, $user_id);

        // Determine if this is a champion (not a trainer offering public training)
        $is_champion = in_array('Certified measureQuick Champion', $certifications, true);

        // Get event count (cached)
        $event_count = get_post_meta($profile_id, 'cached_event_count', true);
        if (empty($event_count)) {
            $event_count = 0;
        }

        return [
            'id' => $profile_id,
            'type' => 'trainer',
            'is_champion' => $is_champion,
            'lat' => floatval($lat),
            'lng' => floatval($lng),
            'name' => get_post_meta($profile_id, 'trainer_display_name', true),
            'city' => $is_champion ? '' : get_post_meta($profile_id, 'trainer_city', true),
            'state' => get_post_meta($profile_id, 'trainer_state', true),
            'certifications' => $certifications,
            'certification' => !empty($certifications) ? $certifications[0] : '',
            'image' => get_post_meta($profile_id, 'profile_image_url', true),
            'profile_id' => $profile_id,
            'user_id' => intval($user_id),
            'event_count' => intval($event_count)
        ];
    }

    /**
     * Format venue data for map marker
     *
     * @param int $venue_id Venue post ID
     * @return array Formatted marker data
     */
    private function format_venue_marker(int $venue_id): array {
        // Try our custom coordinates first, then TEC's
        $lat = get_post_meta($venue_id, 'venue_latitude', true);
        $lng = get_post_meta($venue_id, 'venue_longitude', true);

        if (empty($lat) || empty($lng)) {
            $lat = get_post_meta($venue_id, '_VenueLat', true);
            $lng = get_post_meta($venue_id, '_VenueLng', true);
        }

        // Get venue details from TEC
        $city = get_post_meta($venue_id, '_VenueCity', true);
        $state = get_post_meta($venue_id, '_VenueStateProvince', true) ?: get_post_meta($venue_id, '_VenueState', true);
        $address = get_post_meta($venue_id, '_VenueAddress', true);

        // Count upcoming events at this venue
        $upcoming_events_count = $this->count_venue_upcoming_events($venue_id);

        return [
            'id' => $venue_id,
            'type' => 'venue',
            'lat' => floatval($lat),
            'lng' => floatval($lng),
            'name' => html_entity_decode(get_the_title($venue_id), ENT_QUOTES, 'UTF-8'),
            'address' => $address,
            'city' => $city,
            'state' => $state,
            'upcoming_events' => $upcoming_events_count
        ];
    }

    /**
     * Get trainer certifications
     *
     * @param int $profile_id Profile post ID
     * @param int $user_id User ID
     * @return array List of certification names
     */
    private function get_trainer_certifications(int $profile_id, int $user_id): array {
        $certifications = [];

        // Try new certification system
        if (class_exists('HVAC_Trainer_Certification_Manager')) {
            $cert_manager = HVAC_Trainer_Certification_Manager::instance();
            $trainer_certs = $cert_manager->get_trainer_certifications($user_id);

            foreach ($trainer_certs as $cert) {
                $cert_type = get_post_meta($cert->ID, 'certification_type', true);
                $status = get_post_meta($cert->ID, 'status', true) ?: 'active';
                $expiration = get_post_meta($cert->ID, 'expiration_date', true);

                // Only include active, non-expired certifications
                $is_expired = $expiration && strtotime($expiration) < time();
                if ($status === 'active' && !$is_expired && !empty($cert_type)) {
                    $certifications[] = $cert_type;
                }
            }
        }

        // Fallback to legacy certification
        if (empty($certifications)) {
            $legacy = get_post_meta($profile_id, 'certification_type', true);
            if (!empty($legacy)) {
                $certifications[] = $legacy;
            }
        }

        return array_unique($certifications);
    }

    /**
     * Count upcoming events at a venue
     *
     * @param int $venue_id Venue post ID
     * @return int Number of upcoming events
     */
    private function count_venue_upcoming_events(int $venue_id): int {
        if (!function_exists('tribe_get_events')) {
            return 0;
        }

        $events = tribe_get_events([
            'eventDisplay' => 'upcoming',
            'posts_per_page' => -1,
            'venue' => $venue_id,
            'fields' => 'ids'
        ]);

        return is_array($events) ? count($events) : 0;
    }

    /**
     * Get full trainer profile for modal
     *
     * @param int $profile_id Profile post ID
     * @return array|null Trainer data or null if not found
     */
    public function get_trainer_full_profile(int $profile_id): ?array {
        $profile = get_post($profile_id);
        if (!$profile || $profile->post_type !== 'trainer_profile') {
            return null;
        }

        $user_id = get_post_meta($profile_id, 'user_id', true);

        // Get basic marker data
        $data = $this->format_trainer_marker($profile_id);

        // Add additional details for modal
        $data['company'] = get_post_meta($profile_id, 'company_name', true);
        $data['bio'] = get_post_meta($profile_id, 'trainer_bio', true);
        $data['training_formats'] = $this->get_meta_array($profile_id, 'training_formats');
        $data['training_locations'] = $this->get_meta_array($profile_id, 'training_locations');

        // Get upcoming events
        $data['upcoming_events'] = $this->get_trainer_upcoming_events($user_id);

        return $data;
    }

    /**
     * Get full venue info
     *
     * @param int $venue_id Venue post ID
     * @return array|null Venue data or null if not found
     */
    public function get_venue_full_info(int $venue_id): ?array {
        $venue = get_post($venue_id);
        if (!$venue || $venue->post_type !== 'tribe_venue') {
            return null;
        }

        $data = $this->format_venue_marker($venue_id);

        // Add additional details
        $data['zip'] = get_post_meta($venue_id, '_VenueZip', true);
        $data['country'] = get_post_meta($venue_id, '_VenueCountry', true);
        $data['phone'] = get_post_meta($venue_id, '_VenuePhone', true);
        $data['website'] = get_post_meta($venue_id, '_VenueURL', true);
        $data['description'] = $venue->post_content;
        $data['capacity'] = get_post_meta($venue_id, '_VenueCapacity', true);

        // Get equipment and amenities taxonomies
        $equipment = wp_get_post_terms($venue_id, 'venue_equipment', ['fields' => 'names']);
        $data['equipment'] = is_wp_error($equipment) ? [] : $equipment;

        $amenities = wp_get_post_terms($venue_id, 'venue_amenities', ['fields' => 'names']);
        $data['amenities'] = is_wp_error($amenities) ? [] : $amenities;

        // Get POC (Point of Contact) information
        $data['poc_user_id'] = get_post_meta($venue_id, '_venue_poc_user_id', true);
        $data['poc_name'] = get_post_meta($venue_id, '_venue_poc_name', true);
        $data['poc_email'] = get_post_meta($venue_id, '_venue_poc_email', true);

        // If no POC meta, use post author as fallback
        if (empty($data['poc_user_id'])) {
            $data['poc_user_id'] = $venue->post_author;
            $author = get_userdata($venue->post_author);
            if ($author) {
                $data['poc_name'] = $author->display_name;
                $data['poc_email'] = $author->user_email;
            }
        }

        // Get upcoming events list
        if (function_exists('tribe_get_events')) {
            $events = tribe_get_events([
                'eventDisplay' => 'upcoming',
                'posts_per_page' => 5,
                'venue' => $venue_id
            ]);

            $data['events'] = [];
            foreach ($events as $event) {
                $data['events'][] = [
                    'id' => $event->ID,
                    'title' => html_entity_decode($event->post_title, ENT_QUOTES, 'UTF-8'),
                    'date' => tribe_get_start_date($event->ID, false, 'M j, Y'),
                    'url' => get_permalink($event->ID)
                ];
            }
        }

        return $data;
    }

    /**
     * Get trainer's upcoming events
     *
     * @param int $user_id User ID
     * @param int $limit Maximum events to return
     * @return array Upcoming events
     */
    private function get_trainer_upcoming_events(int $user_id, int $limit = 5): array {
        if (!function_exists('tribe_get_events')) {
            return [];
        }

        $events = tribe_get_events([
            'author' => $user_id,
            'eventDisplay' => 'upcoming',
            'posts_per_page' => $limit
        ]);

        $formatted = [];
        foreach ($events as $event) {
            $formatted[] = [
                'id' => $event->ID,
                'title' => html_entity_decode($event->post_title, ENT_QUOTES, 'UTF-8'),
                'date' => tribe_get_start_date($event->ID, false, 'M j, Y'),
                'url' => get_permalink($event->ID)
            ];
        }

        return $formatted;
    }

    /**
     * Get approved user IDs for filtering trainer profiles
     *
     * @return array User IDs
     */
    private function get_approved_user_ids(): array {
        $user_query = new WP_User_Query([
            'meta_query' => [
                [
                    'key' => 'account_status',
                    'value' => ['approved', 'active', 'inactive'],
                    'compare' => 'IN'
                ]
            ],
            'fields' => 'ID'
        ]);

        return $user_query->get_results();
    }

    /**
     * Get meta value as array (handles comma-separated or serialized)
     *
     * @param int $post_id Post ID
     * @param string $meta_key Meta key
     * @return array Values
     */
    private function get_meta_array(int $post_id, string $meta_key): array {
        $value = get_post_meta($post_id, $meta_key, true);

        if (empty($value)) {
            return [];
        }

        if (is_array($value)) {
            return $value;
        }

        // Handle comma-separated
        if (strpos($value, ',') !== false) {
            return array_map('trim', explode(',', $value));
        }

        return [$value];
    }

    /**
     * Calculate distance between two coordinates using Haversine formula
     *
     * @param float $lat1 First latitude
     * @param float $lng1 First longitude
     * @param float $lat2 Second latitude
     * @param float $lng2 Second longitude
     * @return float Distance in kilometers
     */
    private function calculate_distance(float $lat1, float $lng1, float $lat2, float $lng2): float {
        $earth_radius = 6371; // km

        $lat1_rad = deg2rad($lat1);
        $lat2_rad = deg2rad($lat2);
        $delta_lat = deg2rad($lat2 - $lat1);
        $delta_lng = deg2rad($lng2 - $lng1);

        $a = sin($delta_lat / 2) * sin($delta_lat / 2) +
             cos($lat1_rad) * cos($lat2_rad) *
             sin($delta_lng / 2) * sin($delta_lng / 2);

        $c = 2 * atan2(sqrt($a), sqrt(1 - $a));

        return $earth_radius * $c;
    }

    /**
     * Get unique state options from trainers
     *
     * @return array State options
     */
    public function get_state_options(): array {
        // Check cache first
        $cache_key = 'filter_state_options';
        $cached = wp_cache_get($cache_key, $this->cache_group);

        if ($cached !== false) {
            return $cached;
        }

        global $wpdb;

        $states = $wpdb->get_col("
            SELECT DISTINCT pm.meta_value
            FROM {$wpdb->postmeta} pm
            INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
            WHERE pm.meta_key = 'trainer_state'
            AND p.post_type = 'trainer_profile'
            AND p.post_status = 'publish'
            AND pm.meta_value != ''
            ORDER BY pm.meta_value ASC
        ");

        $states = array_filter($states);

        // Cache for 1 hour
        wp_cache_set($cache_key, $states, $this->cache_group, $this->cache_expiration);

        return $states;
    }

    /**
     * Get certification type options
     *
     * @return array Certification options
     */
    public function get_certification_options(): array {
        // Check cache first
        $cache_key = 'filter_certification_options';
        $cached = wp_cache_get($cache_key, $this->cache_group);

        if ($cached !== false) {
            return $cached;
        }

        global $wpdb;

        $certs = $wpdb->get_col("
            SELECT DISTINCT pm.meta_value
            FROM {$wpdb->postmeta} pm
            INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
            WHERE pm.meta_key = 'certification_type'
            AND p.post_type = 'trainer_profile'
            AND p.post_status = 'publish'
            AND pm.meta_value != ''
            ORDER BY pm.meta_value ASC
        ");

        $certs = array_filter($certs);

        // Cache for 1 hour
        wp_cache_set($cache_key, $certs, $this->cache_group, $this->cache_expiration);

        return $certs;
    }

    /**
     * Get training format options
     *
     * @return array Training format options
     */
    public function get_training_format_options(): array {
        // Check cache first
        $cache_key = 'filter_format_options';
        $cached = wp_cache_get($cache_key, $this->cache_group);

        if ($cached !== false) {
            return $cached;
        }

        global $wpdb;

        $formats_raw = $wpdb->get_col("
            SELECT DISTINCT pm.meta_value
            FROM {$wpdb->postmeta} pm
            INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
            WHERE pm.meta_key = 'training_formats'
            AND p.post_type = 'trainer_profile'
            AND p.post_status = 'publish'
            AND pm.meta_value != ''
        ");

        // Process comma-separated values
        $formats = [];
        foreach ($formats_raw as $format_string) {
            if (empty($format_string)) continue;
            $individual = array_map('trim', explode(',', $format_string));
            $formats = array_merge($formats, $individual);
        }

        $formats = array_unique(array_filter($formats));
        sort($formats);

        // Cache for 1 hour
        wp_cache_set($cache_key, $formats, $this->cache_group, $this->cache_expiration);

        return $formats;
    }

    /**
     * Get event markers for map
     *
     * @param array $filters Optional filters (state, search, lat/lng/radius, include_past)
     * @return array Event markers data
     */
    public function get_event_markers(array $filters = []): array {
        // Check if TEC is active
        if (!function_exists('tribe_get_events')) {
            return [];
        }

        // Generate cache key
        $cache_key = 'events_' . md5(serialize($filters));
        $cached = wp_cache_get($cache_key, $this->cache_group);

        if ($cached !== false && empty($filters)) {
            return $cached;
        }

        // Determine if we need to filter by venue first
        $venue_ids = null;
        if (!empty($filters['state']) || !empty($filters['search']) ||
            (!empty($filters['lat']) && !empty($filters['lng']) && !empty($filters['radius']))) {
            $venue_ids = $this->get_venue_ids_by_filters($filters);

            // If venue filters are active but no matching venues found, return empty
            if (!empty($filters['state']) && empty($venue_ids)) {
                return [];
            }
        }

        // Build event query args
        $include_past = !empty($filters['include_past']);

        $event_args = [
            'posts_per_page' => 100,
            'post_status' => 'publish',
            'orderby' => 'event_date',
            'order' => 'ASC',
        ];

        if ($include_past) {
            // Custom date range: past 6 months to future 1 year
            $event_args['eventDisplay'] = 'custom';
            $event_args['start_date'] = date('Y-m-d', strtotime('-6 months'));
            $event_args['end_date'] = date('Y-m-d', strtotime('+1 year'));
        } else {
            // Default: upcoming and ongoing events only
            $event_args['eventDisplay'] = 'list';
        }

        // Apply venue filter if we have filtered venue IDs
        if ($venue_ids !== null && !empty($venue_ids)) {
            $event_args['venue'] = $venue_ids;
        }

        $events = tribe_get_events($event_args);
        $markers = [];

        // Batch load meta for performance
        if (!empty($events)) {
            $event_ids = wp_list_pluck($events, 'ID');
            update_postmeta_cache($event_ids);
        }

        foreach ($events as $event) {
            $marker = $this->format_event_marker($event);

            // Skip events without valid coordinates
            if (empty($marker) || empty($marker['lat']) || empty($marker['lng'])) {
                continue;
            }

            // Apply search filter to event title and venue
            if (!empty($filters['search'])) {
                $search = strtolower($filters['search']);
                $title_match = stripos($marker['title'], $search) !== false;
                $venue_match = stripos($marker['venue_name'] ?? '', $search) !== false;
                $city_match = stripos($marker['venue_city'] ?? '', $search) !== false;

                if (!$title_match && !$venue_match && !$city_match) {
                    continue;
                }
            }

            // Apply proximity filter
            if (!empty($filters['lat']) && !empty($filters['lng']) && !empty($filters['radius'])) {
                $distance = $this->calculate_distance(
                    $filters['lat'],
                    $filters['lng'],
                    $marker['lat'],
                    $marker['lng']
                );
                if ($distance > $filters['radius']) {
                    continue;
                }
                $marker['distance'] = round($distance, 1);
            }

            $markers[] = $marker;
        }

        // Sort by distance if proximity search, otherwise by date
        if (!empty($filters['lat']) && !empty($filters['lng'])) {
            usort($markers, function($a, $b) {
                return ($a['distance'] ?? 0) <=> ($b['distance'] ?? 0);
            });
        }

        // Cache if no filters
        if (empty($filters)) {
            wp_cache_set($cache_key, $markers, $this->cache_group, $this->cache_expiration);
        }

        return $markers;
    }

    /**
     * Format event data for map marker
     *
     * @param WP_Post $event Event post object
     * @return array|null Formatted marker data or null if invalid
     */
    private function format_event_marker(WP_Post $event): ?array {
        // Get venue ID
        $venue_id = get_post_meta($event->ID, '_EventVenueID', true);

        // Skip events without venue
        if (empty($venue_id)) {
            return null;
        }

        // Check if event should show on map
        $show_map = get_post_meta($event->ID, '_EventShowMap', true);
        if ($show_map === 'false' || $show_map === '0') {
            // Virtual-only event with no map display
            return null;
        }

        // Get venue coordinates
        $lat = get_post_meta($venue_id, 'venue_latitude', true);
        $lng = get_post_meta($venue_id, 'venue_longitude', true);

        if (empty($lat) || empty($lng)) {
            $lat = get_post_meta($venue_id, '_VenueLat', true);
            $lng = get_post_meta($venue_id, '_VenueLng', true);
        }

        // Skip if no coordinates
        if (empty($lat) || empty($lng)) {
            return null;
        }

        // Get venue details
        $venue_name = get_the_title($venue_id);
        $venue_city = get_post_meta($venue_id, '_VenueCity', true);
        $venue_state = get_post_meta($venue_id, '_VenueStateProvince', true)
            ?: get_post_meta($venue_id, '_VenueState', true);

        // Get event dates
        $start_date = tribe_get_start_date($event->ID, false, 'M j, Y');
        $end_date = tribe_get_end_date($event->ID, false, 'M j, Y');
        $start_time = tribe_get_start_date($event->ID, false, 'g:i A');
        $is_all_day = tribe_event_is_all_day($event->ID);

        // Determine if past event
        $event_end = tribe_get_end_date($event->ID, false, 'U');
        $is_past = $event_end < time();

        // Get cost (decode HTML entities so JS doesn't double-escape &#x24; → $)
        $cost = html_entity_decode(tribe_get_formatted_cost($event->ID), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        if (empty($cost)) {
            $cost = 'Free';
        }

        // Get excerpt (sanitized)
        $excerpt = wp_strip_all_tags($event->post_content);
        $excerpt = wp_trim_words($excerpt, 20, '...');

        return [
            'id' => $event->ID,
            'type' => 'event',
            'lat' => floatval($lat),
            'lng' => floatval($lng),
            'title' => esc_html(html_entity_decode(get_the_title($event->ID), ENT_QUOTES, 'UTF-8')),
            'excerpt' => $excerpt,
            'start_date' => $start_date,
            'end_date' => $end_date,
            'start_time' => $is_all_day ? '' : $start_time,
            'is_all_day' => $is_all_day,
            'venue_id' => intval($venue_id),
            'venue_name' => esc_html(html_entity_decode($venue_name, ENT_QUOTES, 'UTF-8')),
            'venue_city' => $venue_city,
            'venue_state' => $venue_state,
            'cost' => $cost,
            'url' => esc_url(get_permalink($event->ID)),
            'is_past' => $is_past
        ];
    }

    /**
     * Get venue IDs matching filters (for event filtering)
     *
     * @param array $filters Filters (state, search, lat/lng/radius)
     * @return array Matching venue IDs
     */
    private function get_venue_ids_by_filters(array $filters): array {
        if (!function_exists('tribe_get_venue')) {
            return [];
        }

        $query_args = [
            'post_type' => 'tribe_venue',
            'posts_per_page' => -1,
            'post_status' => 'publish',
            'fields' => 'ids',
            'meta_query' => [
                'relation' => 'AND',
            ]
        ];

        // Add state filter
        if (!empty($filters['state'])) {
            $query_args['meta_query'][] = [
                'relation' => 'OR',
                [
                    'key' => '_VenueStateProvince',
                    'value' => sanitize_text_field($filters['state']),
                    'compare' => '='
                ],
                [
                    'key' => '_VenueState',
                    'value' => sanitize_text_field($filters['state']),
                    'compare' => '='
                ]
            ];
        }

        // Add search filter (venue name or city)
        if (!empty($filters['search'])) {
            $query_args['meta_query'][] = [
                'relation' => 'OR',
                [
                    'key' => '_VenueCity',
                    'value' => sanitize_text_field($filters['search']),
                    'compare' => 'LIKE'
                ]
            ];
            // Also search by venue name
            $query_args['s'] = sanitize_text_field($filters['search']);
        }

        $query = new WP_Query($query_args);
        $venue_ids = $query->posts;

        // Apply proximity filter if needed
        if (!empty($filters['lat']) && !empty($filters['lng']) && !empty($filters['radius']) && !empty($venue_ids)) {
            $filtered_ids = [];
            foreach ($venue_ids as $venue_id) {
                $lat = get_post_meta($venue_id, 'venue_latitude', true) ?: get_post_meta($venue_id, '_VenueLat', true);
                $lng = get_post_meta($venue_id, 'venue_longitude', true) ?: get_post_meta($venue_id, '_VenueLng', true);

                if (!empty($lat) && !empty($lng)) {
                    $distance = $this->calculate_distance(
                        $filters['lat'],
                        $filters['lng'],
                        floatval($lat),
                        floatval($lng)
                    );
                    if ($distance <= $filters['radius']) {
                        $filtered_ids[] = $venue_id;
                    }
                }
            }
            $venue_ids = $filtered_ids;
        }

        return $venue_ids;
    }

    /**
     * Clear event cache
     */
    public function clear_event_cache(): void {
        if (function_exists('wp_cache_delete_group')) {
            wp_cache_delete_group($this->cache_group);
        } else {
            wp_cache_flush();
        }
    }

    /**
     * Clear trainer cache
     */
    public function clear_trainer_cache(): void {
        if (function_exists('wp_cache_delete_group')) {
            wp_cache_delete_group($this->cache_group);
        } else {
            wp_cache_flush();
        }
    }

    /**
     * Clear venue cache
     */
    public function clear_venue_cache(): void {
        if (function_exists('wp_cache_delete_group')) {
            wp_cache_delete_group($this->cache_group);
        } else {
            wp_cache_flush();
        }
    }
}
