<?php
if (!defined('ABSPATH')) {
    exit;
}

trait EEP_Content_Trait {
    private function create_rsvp_table() {
        global $wpdb;
        $table = $wpdb->prefix . self::RSVP_TABLE;
        $charset = $wpdb->get_charset_collate();
        $sql = "CREATE TABLE {$table} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            post_id bigint(20) unsigned NOT NULL,
            user_id bigint(20) unsigned NOT NULL,
            status varchar(20) NOT NULL DEFAULT 'yes',
            created_at datetime NOT NULL,
            updated_at datetime NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY post_user (post_id,user_id),
            KEY event_id (post_id),
            KEY user_id (user_id),
            KEY post_status (post_id,status),
            KEY user_status (user_id,status)
        ) {$charset};";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta($sql);
    }


    private function create_inbox_tables() {
        global $wpdb;
        $threads_table = $wpdb->prefix . self::INBOX_THREADS_TABLE;
        $messages_table = $wpdb->prefix . self::INBOX_MESSAGES_TABLE;
        $charset = $wpdb->get_charset_collate();

        $threads_sql = "CREATE TABLE {$threads_table} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            post_id bigint(20) unsigned NOT NULL,
            participant_a bigint(20) unsigned NOT NULL,
            participant_b bigint(20) unsigned NOT NULL,
            subject varchar(191) NOT NULL DEFAULT '',
            created_at datetime NOT NULL,
            updated_at datetime NOT NULL,
            last_message_id bigint(20) unsigned NOT NULL DEFAULT 0,
            last_sender_id bigint(20) unsigned NOT NULL DEFAULT 0,
            PRIMARY KEY  (id),
            UNIQUE KEY event_pair (post_id,participant_a,participant_b),
            KEY participant_a (participant_a),
            KEY participant_b (participant_b),
            KEY updated_at (updated_at)
        ) {$charset};";

        $messages_sql = "CREATE TABLE {$messages_table} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            thread_id bigint(20) unsigned NOT NULL,
            post_id bigint(20) unsigned NOT NULL,
            sender_id bigint(20) unsigned NOT NULL,
            recipient_id bigint(20) unsigned NOT NULL,
            subject varchar(191) NOT NULL DEFAULT '',
            body longtext NOT NULL,
            attachment_url text NULL,
            created_at datetime NOT NULL,
            read_at datetime NULL DEFAULT NULL,
            PRIMARY KEY  (id),
            KEY thread_id (thread_id),
            KEY post_id (post_id),
            KEY sender_id (sender_id),
            KEY recipient_unread (recipient_id,read_at),
            KEY created_at (created_at)
        ) {$charset};";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta($threads_sql);
        dbDelta($messages_sql);
    }


    public function create_chatbot_embeddings_table() {
        global $wpdb;
        $table = $wpdb->prefix . self::CHATBOT_EMBED_TABLE;
        $charset = $wpdb->get_charset_collate();
        $sql = "CREATE TABLE {$table} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            post_id bigint(20) unsigned NOT NULL,
            content_hash char(64) NOT NULL,
            embedding_model varchar(64) NOT NULL,
            embedding_dimensions smallint(5) unsigned NOT NULL DEFAULT 0,
            embedding longtext NOT NULL,
            chunk_text mediumtext NOT NULL,
            created_at datetime NOT NULL,
            updated_at datetime NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY post_id (post_id),
            KEY content_hash (content_hash),
            KEY updated_at (updated_at)
        ) {$charset};";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta($sql);
    }


    public function create_analytics_table() {
        global $wpdb;
        $table = $wpdb->prefix . self::ANALYTICS_TABLE;
        $charset = $wpdb->get_charset_collate();
        $sql = "CREATE TABLE {$table} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            event_id bigint(20) unsigned NOT NULL DEFAULT 0,
            owner_id bigint(20) unsigned NOT NULL DEFAULT 0,
            user_id bigint(20) unsigned NOT NULL DEFAULT 0,
            metric varchar(64) NOT NULL,
            context varchar(64) NOT NULL DEFAULT '',
            meta_key varchar(64) NOT NULL DEFAULT '',
            meta_value varchar(191) NOT NULL DEFAULT '',
            value decimal(18,4) NOT NULL DEFAULT 1.0000,
            created_at datetime NOT NULL,
            PRIMARY KEY  (id),
            KEY event_metric_date (event_id, metric, created_at),
            KEY owner_metric_date (owner_id, metric, created_at),
            KEY user_metric_date (user_id, metric, created_at),
            KEY metric_date (metric, created_at),
            KEY context_metric_date (context, metric, created_at),
            KEY metric_meta_lookup (metric, meta_key, meta_value(64))
        ) {$charset};";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta($sql);
    }


    public function maybe_upgrade_schema() {
        $db_version = (string) get_option('eep_db_version', '');
        if ($db_version === EEP_DB_VERSION) {
            $this->ensure_performance_indexes();
            $this->create_inbox_tables();
            $this->create_chatbot_embeddings_table();
            $this->create_analytics_table();
            if (method_exists($this, 'create_boost_orders_table')) {
                $this->create_boost_orders_table();
            }
            if (method_exists($this, 'create_ticket_orders_table')) {
                $this->create_ticket_orders_table();
            }
            $this->schedule_reminders();
            if (method_exists($this, 'schedule_boost_renewals')) {
                $this->schedule_boost_renewals();
            }
            update_option('eep_plugin_version', self::VERSION, false);
            return;
        }
        $this->create_rsvp_table();
        $this->create_inbox_tables();
        $this->create_chatbot_embeddings_table();
        $this->create_analytics_table();
        if (method_exists($this, 'create_boost_orders_table')) {
            $this->create_boost_orders_table();
        }
        if (method_exists($this, 'create_ticket_orders_table')) {
            $this->create_ticket_orders_table();
        }
        $this->ensure_performance_indexes();
        update_option('eep_db_version', EEP_DB_VERSION, false);
        update_option('eep_plugin_version', self::VERSION, false);
        $this->schedule_reminders();
        if (method_exists($this, 'schedule_boost_renewals')) {
            $this->schedule_boost_renewals();
        }
    }


    private function ensure_performance_indexes() {
        global $wpdb;
        $postmeta_table = (string) $wpdb->postmeta;
        $posts_table = (string) $wpdb->posts;
        if ($postmeta_table === '' || $posts_table === '') {
            return;
        }

        $meta_indexes = $wpdb->get_col("SHOW INDEX FROM {$postmeta_table}", 2);
        if (!is_array($meta_indexes)) {
            $meta_indexes = [];
        }
        $meta_indexes = array_map('strval', $meta_indexes);

        $meta_index_sql = [
            'eep_meta_start' => "ALTER TABLE {$postmeta_table} ADD INDEX eep_meta_start (meta_key(32), meta_value(32))",
            'eep_meta_lat' => "ALTER TABLE {$postmeta_table} ADD INDEX eep_meta_lat (meta_key(16), meta_value(20))",
            'eep_meta_lng' => "ALTER TABLE {$postmeta_table} ADD INDEX eep_meta_lng (meta_key(16), meta_value(20))",
        ];
        foreach ($meta_index_sql as $index_name => $sql) {
            if (in_array($index_name, $meta_indexes, true)) {
                continue;
            }
            $result = $wpdb->query($sql); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
            if ($result === false && !empty($wpdb->last_error)) {
                $this->append_runtime_log('warning', 'Could not create performance index', [
                    'index' => $index_name,
                    'table' => $postmeta_table,
                    'error' => (string) $wpdb->last_error,
                ]);
            }
        }

        $post_indexes = $wpdb->get_col("SHOW INDEX FROM {$posts_table}", 2);
        if (!is_array($post_indexes)) {
            $post_indexes = [];
        }
        $post_indexes = array_map('strval', $post_indexes);
        if (!in_array('eep_posts_status_author', $post_indexes, true)) {
            $result = $wpdb->query("ALTER TABLE {$posts_table} ADD INDEX eep_posts_status_author (post_type, post_status, post_author)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
            if ($result === false && !empty($wpdb->last_error)) {
                $this->append_runtime_log('warning', 'Could not create performance index', [
                    'index' => 'eep_posts_status_author',
                    'table' => $posts_table,
                    'error' => (string) $wpdb->last_error,
                ]);
            }
        }
    }
    public function register_content() {
        $brand_name = method_exists($this, 'get_white_label_brand_name')
            ? (string) $this->get_white_label_brand_name()
            : __('nexdirectory', 'nexdirectory');
        $ui_labels = method_exists($this, 'get_site_profile_ui_labels') ? (array) $this->get_site_profile_ui_labels() : [];
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $plural_label = sanitize_text_field((string) ($ui_labels['plural'] ?? __('Events', 'nexdirectory')));
        $taxonomy_type_singular = sanitize_text_field((string) ($ui_labels['taxonomy_type_singular'] ?? __('Event Type', 'nexdirectory')));
        $taxonomy_type_plural = sanitize_text_field((string) ($ui_labels['taxonomy_type_plural'] ?? __('Event Types', 'nexdirectory')));
        $taxonomy_mode_singular = sanitize_text_field((string) ($ui_labels['taxonomy_mode_singular'] ?? __('Event Mode', 'nexdirectory')));
        $taxonomy_mode_plural = sanitize_text_field((string) ($ui_labels['taxonomy_mode_plural'] ?? __('Event Modes', 'nexdirectory')));
        $taxonomy_tag_singular = sanitize_text_field((string) ($ui_labels['taxonomy_tag_singular'] ?? __('Event Tag', 'nexdirectory')));
        $taxonomy_tag_plural = sanitize_text_field((string) ($ui_labels['taxonomy_tag_plural'] ?? __('Event Tags', 'nexdirectory')));
        register_post_type(self::POST_TYPE, [
            'labels' => [
                'name' => trim($brand_name . ' ' . $plural_label),
                'singular_name' => $singular_label,
                'add_new' => sprintf(__('Add New %s', 'nexdirectory'), $singular_label),
                'add_new_item' => sprintf(__('Add New %s', 'nexdirectory'), $singular_label),
                'edit_item' => sprintf(__('Edit %s', 'nexdirectory'), $singular_label),
            ],
            'public' => true,
            'has_archive' => false,
            'rewrite' => ['slug' => 'events'],
            'supports' => ['title', 'editor', 'excerpt', 'thumbnail', 'author'],
            'show_in_rest' => true,
            'menu_icon' => 'dashicons-calendar-alt',
            'capability_type' => 'post',
            'map_meta_cap' => true,
        ]);

        register_taxonomy(self::TAX_TYPE, self::POST_TYPE, [
            'labels' => [
                'name' => $taxonomy_type_plural,
                'singular_name' => $taxonomy_type_singular,
            ],
            'hierarchical' => true,
            'show_admin_column' => true,
            'rewrite' => ['slug' => 'event-type'],
            'show_in_rest' => true,
        ]);

        register_taxonomy(self::TAX_MODE, self::POST_TYPE, [
            'labels' => [
                'name' => $taxonomy_mode_plural,
                'singular_name' => $taxonomy_mode_singular,
            ],
            'hierarchical' => true,
            'show_admin_column' => true,
            'rewrite' => ['slug' => 'event-mode'],
            'show_in_rest' => true,
        ]);

        register_taxonomy(self::TAX_TAG, self::POST_TYPE, [
            'labels' => [
                'name' => $taxonomy_tag_plural,
                'singular_name' => $taxonomy_tag_singular,
            ],
            'hierarchical' => false,
            'show_admin_column' => true,
            'rewrite' => ['slug' => 'event-tag'],
            'show_in_rest' => true,
        ]);

        register_taxonomy(self::TAX_PRICE, self::POST_TYPE, [
            'labels' => [
                'name' => __('Price Ranges', 'nexdirectory'),
                'singular_name' => __('Price Range', 'nexdirectory'),
            ],
            'hierarchical' => true,
            'show_admin_column' => true,
            'rewrite' => ['slug' => 'event-price-range'],
            'show_in_rest' => true,
        ]);

        foreach ($this->get_custom_taxonomy_definitions() as $taxonomy_row) {
            $taxonomy_slug = sanitize_key((string) ($taxonomy_row['slug'] ?? ''));
            if ($taxonomy_slug === '' || taxonomy_exists($taxonomy_slug)) {
                continue;
            }
            $taxonomy_label = sanitize_text_field((string) ($taxonomy_row['label'] ?? $taxonomy_slug));
            $taxonomy_hierarchical = (string) ($taxonomy_row['hierarchical'] ?? '1') === '1';
            $rewrite_slug = eep_sanitize_title(str_replace('eep_', '', $taxonomy_slug));
            if ($rewrite_slug === '') {
                $rewrite_slug = $taxonomy_slug;
            }

            register_taxonomy($taxonomy_slug, self::POST_TYPE, [
                'labels' => [
                    'name' => $taxonomy_label,
                    'singular_name' => $taxonomy_label,
                ],
                'hierarchical' => $taxonomy_hierarchical,
                'show_admin_column' => true,
                'show_ui' => true,
                'rewrite' => ['slug' => $rewrite_slug],
                'show_in_rest' => true,
            ]);
        }

        $this->maybe_flush_rewrite_rules_once();
    }


    private function maybe_flush_rewrite_rules_once() {
        $signature = 'eep-rewrite-archive-off-v3';
        $stored = (string) get_option('eep_rewrite_signature', '');
        if ($stored === $signature) {
            return;
        }
        $this->ensure_default_pages();
        flush_rewrite_rules(false);
        update_option('eep_rewrite_signature', $signature, false);
    }


    public function register_shortcodes() {
        $shortcodes = [
            'eep_events_archive' => 'render_events_archive',
            'eep_events_map' => 'render_events_map',
            'eep_events_calendar' => 'render_events_calendar',
            'eep_listings_archive' => 'render_events_archive',
            'eep_listings_map' => 'render_events_map',
            'eep_listings_calendar' => 'render_events_calendar',
            'eep_events_query_grid' => 'render_events_query_grid',
            'eep_events_bento_grid' => 'render_events_bento_grid',
            'eep_events_ajax_search' => 'render_events_ajax_search',
            'eep_event_chatbot' => 'render_event_chatbot',
            'eep_event_webapp' => 'render_event_webapp',
            'eep_quick_search' => 'render_quick_search',
            'eep_submit_event' => 'render_submit_event',
            'eep_submit' => 'render_submit_event',
            'eep_my_events' => 'render_my_events',
            'eep_my_listings' => 'render_my_events',
            'eep_boost_posts' => 'render_boost_posts',
            'eep_my_tickets' => 'render_my_tickets',
            'eep_saved_listings' => 'render_my_tickets',
            'eep_whos_attending' => 'render_whos_attending',
            'eep_attendees' => 'render_whos_attending',
            'eep_owner_analytics' => 'render_owner_analytics',
            'eep_notifications' => 'render_notifications_center',
            'eep_whats_new' => 'render_whats_new',
            'eep_account' => 'render_account',
            'eep_auth' => 'render_auth',
            'eep_auth_links' => 'render_auth_links',
            'eep_user_management' => 'render_user_management',
            'eep_moderation_queue' => 'render_moderation_queue',
            'eep_organizers' => 'render_organizers',
            'eep_claim_listing' => 'render_claim_listing',
        ];

        foreach ($shortcodes as $tag => $method) {
            if (method_exists($this, $method)) {
                add_shortcode($tag, [$this, 'render_registered_shortcode']);
            }
        }
        $this->register_builder_compatibility_shortcodes();
    }


    private function register_builder_compatibility_shortcodes() {
        $aliases = [
            'nexdirectory_events_archive' => 'render_events_archive',
            'nexdirectory_events_map' => 'render_events_map',
            'nexdirectory_events_calendar' => 'render_events_calendar',
            'nexdirectory_listings_archive' => 'render_events_archive',
            'nexdirectory_listings_map' => 'render_events_map',
            'nexdirectory_listings_calendar' => 'render_events_calendar',
            'nexdirectory_events_query_grid' => 'render_events_query_grid',
            'nexdirectory_events_bento_grid' => 'render_events_bento_grid',
            'nexdirectory_events_ajax_search' => 'render_events_ajax_search',
            'nexdirectory_event_chatbot' => 'render_event_chatbot',
            'nexdirectory_event_webapp' => 'render_event_webapp',
            'nexdirectory_quick_search' => 'render_quick_search',
            'nexdirectory_submit_event' => 'render_submit_event',
            'nexdirectory_submit' => 'render_submit_event',
            'nexdirectory_my_events' => 'render_my_events',
            'nexdirectory_my_listings' => 'render_my_events',
            'nexdirectory_boost_posts' => 'render_boost_posts',
            'nexdirectory_my_tickets' => 'render_my_tickets',
            'nexdirectory_saved_listings' => 'render_my_tickets',
            'nexdirectory_whos_attending' => 'render_whos_attending',
            'nexdirectory_attendees' => 'render_whos_attending',
            'nexdirectory_owner_analytics' => 'render_owner_analytics',
            'nexdirectory_notifications' => 'render_notifications_center',
            'nexdirectory_whats_new' => 'render_whats_new',
            'nexdirectory_account' => 'render_account',
            'nexdirectory_auth' => 'render_auth',
            'nexdirectory_auth_links' => 'render_auth_links',
            'nexdirectory_user_management' => 'render_user_management',
            'nexdirectory_moderation_queue' => 'render_moderation_queue',
            'nexdirectory_organizers' => 'render_organizers',
            'nexdirectory_claim_listing' => 'render_claim_listing',
        ];
        foreach ($aliases as $tag => $method) {
            if (method_exists($this, $method)) {
                add_shortcode($tag, [$this, 'render_registered_shortcode']);
            }
        }
    }

    /**
     * Central shortcode entrypoint so app wrappers never diverge between shortcode tags.
     *
     * @param array<string, mixed>|array<int, mixed> $atts
     * @param string|null $content
     * @param string $tag
     * @return string
     */
    public function render_registered_shortcode($atts = [], $content = null, $tag = '') {
        $resolved_tag = $this->normalize_shortcode_tag($tag);
        if ($resolved_tag === '') {
            return '';
        }

        $output = $this->render_shortcode_tag($resolved_tag, (array) $atts);
        if (!is_string($output) || $output === '') {
            return '';
        }

        $view = $this->get_shortcode_app_view($resolved_tag);
        if ($view === '') {
            return $output;
        }

        return $this->wrap_app_shortcode_output($output, $view);
    }

    /**
     * Converts legacy/builder shortcode aliases to canonical tags.
     *
     * @param string $tag
     * @return string
     */
    private function normalize_shortcode_tag($tag) {
        $tag = sanitize_key((string) $tag);
        if ($tag === '') {
            return '';
        }
        if (strpos($tag, 'nexdirectory_') === 0) {
            return 'eep_' . substr($tag, strlen('nexdirectory_'));
        }
        return $tag;
    }

    /**
     * App-page shortcode to view-key mapping.
     *
     * @return array<string, string>
     */
    private function get_app_shortcode_view_map() {
        return [
            'eep_events_archive' => 'events',
            'eep_listings_archive' => 'events',
            'eep_events_map' => 'map',
            'eep_listings_map' => 'map',
            'eep_events_calendar' => 'calendar',
            'eep_listings_calendar' => 'calendar',
            'eep_event_webapp' => 'event-webapp',
            'eep_submit_event' => 'submit',
            'eep_submit' => 'submit',
            'eep_my_events' => 'my-events',
            'eep_my_listings' => 'my-events',
            'eep_boost_posts' => 'boost-posts',
            'eep_my_tickets' => 'my-tickets',
            'eep_saved_listings' => 'my-tickets',
            'eep_whos_attending' => 'whos-attending',
            'eep_attendees' => 'whos-attending',
            'eep_owner_analytics' => 'owner-analytics',
            'eep_notifications' => 'notifications',
            'eep_whats_new' => 'whats-new',
            'eep_account' => 'account',
            'eep_auth' => 'auth',
            'eep_user_management' => 'user-management',
            'eep_moderation_queue' => 'moderation-queue',
            'eep_organizers' => 'organizers',
            'eep_claim_listing' => 'claim-listing',
        ];
    }

    /**
     * Resolves app view key for a shortcode, when applicable.
     *
     * @param string $tag
     * @return string
     */
    private function get_shortcode_app_view($tag) {
        $tag = $this->normalize_shortcode_tag($tag);
        $map = $this->get_app_shortcode_view_map();
        return isset($map[$tag]) ? sanitize_key((string) $map[$tag]) : '';
    }

    /**
     * Wraps app shortcodes in a single canonical root container.
     *
     * @param string $output
     * @param string $view
     * @return string
     */
    private function wrap_app_shortcode_output($output, $view) {
        $view_attr = strtoupper(sanitize_key((string) $view));
        return '<div id="eep-app" class="eep-app" data-eep-view="' . esc_attr($view_attr) . '">' . $output . '</div>';
    }


    public function render_shortcode_tag($tag, $atts = []) {
        $tag = $this->normalize_shortcode_tag($tag);
        switch ($tag) {
            case 'eep_events_archive':
            case 'eep_listings_archive':
                return $this->render_events_archive((array) $atts);
            case 'eep_events_map':
            case 'eep_listings_map':
                return $this->render_events_map((array) $atts);
            case 'eep_events_calendar':
            case 'eep_listings_calendar':
                return $this->render_events_calendar((array) $atts);
            case 'eep_events_query_grid':
                return $this->render_events_query_grid((array) $atts);
            case 'eep_events_bento_grid':
                return $this->render_events_bento_grid((array) $atts);
            case 'eep_events_ajax_search':
                return $this->render_events_ajax_search((array) $atts);
            case 'eep_event_chatbot':
                return $this->render_event_chatbot((array) $atts);
            case 'eep_event_webapp':
                return $this->render_event_webapp((array) $atts);
            case 'eep_quick_search':
                return $this->render_quick_search((array) $atts);
            case 'eep_submit_event':
            case 'eep_submit':
                return $this->render_submit_event((array) $atts);
            case 'eep_my_events':
            case 'eep_my_listings':
                return $this->render_my_events((array) $atts);
            case 'eep_boost_posts':
                return $this->render_boost_posts((array) $atts);
            case 'eep_my_tickets':
            case 'eep_saved_listings':
                return $this->render_my_tickets((array) $atts);
            case 'eep_whos_attending':
            case 'eep_attendees':
                return $this->render_whos_attending((array) $atts);
            case 'eep_owner_analytics':
                return $this->render_owner_analytics((array) $atts);
            case 'eep_notifications':
                return $this->render_notifications_center((array) $atts);
            case 'eep_whats_new':
                return $this->render_whats_new((array) $atts);
            case 'eep_account':
                return $this->render_account((array) $atts);
            case 'eep_auth':
                return $this->render_auth((array) $atts);
            case 'eep_auth_links':
                return $this->render_auth_links((array) $atts);
            case 'eep_user_management':
                return $this->render_user_management((array) $atts);
            case 'eep_moderation_queue':
                return $this->render_moderation_queue((array) $atts);
            case 'eep_organizers':
                return $this->render_organizers((array) $atts);
            case 'eep_claim_listing':
                return $this->render_claim_listing((array) $atts);
            default:
                return '';
        }
    }


    private function ensure_default_pages() {
        $ui_labels = method_exists($this, 'get_site_profile_ui_labels') ? (array) $this->get_site_profile_ui_labels() : [];
        $profile_key = method_exists($this, 'get_site_profile_key') ? (string) $this->get_site_profile_key() : 'events';
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $plural_label = sanitize_text_field((string) ($ui_labels['plural'] ?? __('Events', 'nexdirectory')));
        $owner_plural = sanitize_text_field((string) ($ui_labels['owner_plural'] ?? __('Organizers', 'nexdirectory')));
        $archive_title = sanitize_text_field((string) ($ui_labels['archive_title'] ?? sprintf(__('View %s', 'nexdirectory'), $plural_label)));
        $map_title = sanitize_text_field((string) ($ui_labels['map_title'] ?? sprintf(__('%s Map', 'nexdirectory'), $singular_label)));
        $calendar_title = sanitize_text_field((string) ($ui_labels['calendar_title'] ?? sprintf(__('%s Calendar', 'nexdirectory'), $plural_label)));
        $submit_title = sanitize_text_field((string) ($ui_labels['submit_add_title'] ?? sprintf(__('Add %s', 'nexdirectory'), $singular_label)));
        $my_items_title = sanitize_text_field((string) ($ui_labels['my_items_title'] ?? sprintf(__('My %s', 'nexdirectory'), $plural_label)));
        $attending_title = sanitize_text_field((string) ($ui_labels['attending_title'] ?? sprintf(__('Saved %s', 'nexdirectory'), $plural_label)));
        $attendees_title = sanitize_text_field((string) ($ui_labels['is_events_profile'] ?? '1') === '1'
            ? __('Who\'s Attending', 'nexdirectory')
            : sprintf(__('Who Saved This %s', 'nexdirectory'), $singular_label)
        );
        $owner_analytics_title = sanitize_text_field((string) ($ui_labels['owner_analytics_title'] ?? __('Analytics', 'nexdirectory')));
        $boost_post_title = __('Boost Post', 'nexdirectory');

        $pages = [
            [
                'slug' => $this->get_profile_route_slug('events', $profile_key),
                'title' => $archive_title !== '' ? $archive_title : __('Events', 'nexdirectory'),
                'shortcode' => '[eep_listings_archive]',
                'view' => 'events',
            ],
            [
                'slug' => 'event-app',
                'title' => __('Event App', 'nexdirectory'),
                'shortcode' => '[eep_event_webapp]',
                'view' => 'event-webapp',
            ],
            [
                'slug' => $this->get_profile_route_slug('map', $profile_key),
                'title' => $map_title !== '' ? $map_title : __('Map View', 'nexdirectory'),
                'shortcode' => '[eep_listings_map]',
                'view' => 'map',
            ],
            [
                'slug' => $this->get_profile_route_slug('calendar', $profile_key),
                'title' => $calendar_title !== '' ? $calendar_title : __('Events Calendar', 'nexdirectory'),
                'shortcode' => '[eep_listings_calendar]',
                'view' => 'calendar',
            ],
            ['slug' => 'whats-new', 'title' => 'What\'s New', 'shortcode' => '[eep_whats_new]', 'view' => 'whats-new'],
            [
                'slug' => $this->get_profile_route_slug('submit', $profile_key),
                'title' => $submit_title !== '' ? $submit_title : sprintf(__('Add %s', 'nexdirectory'), $singular_label),
                'shortcode' => '[eep_submit]',
                'view' => 'submit',
            ],
            [
                'slug' => $this->get_profile_route_slug('my_items', $profile_key),
                'title' => $my_items_title !== '' ? $my_items_title : sprintf(__('My %s', 'nexdirectory'), $plural_label),
                'shortcode' => '[eep_my_listings]',
                'view' => 'my-events',
            ],
            [
                'slug' => $this->get_profile_route_slug('boost', $profile_key),
                'title' => $boost_post_title,
                'shortcode' => '[eep_boost_posts]',
                'view' => 'boost-posts',
            ],
            [
                'slug' => $this->get_profile_route_slug('analytics', $profile_key),
                'title' => $owner_analytics_title !== '' ? $owner_analytics_title : __('Analytics', 'nexdirectory'),
                'shortcode' => '[eep_owner_analytics]',
                'view' => 'owner-analytics',
            ],
            [
                'slug' => $this->get_profile_route_slug('attending', $profile_key),
                'title' => $attending_title !== '' ? $attending_title : __('Events I\'m Attending', 'nexdirectory'),
                'shortcode' => '[eep_saved_listings]',
                'view' => 'my-tickets',
            ],
            [
                'slug' => $this->get_profile_route_slug('attendees', $profile_key),
                'title' => $attendees_title !== '' ? $attendees_title : __('Who\'s Attending', 'nexdirectory'),
                'shortcode' => '[eep_whos_attending]',
                'view' => 'whos-attending',
            ],
            ['slug' => 'notifications', 'title' => 'Notifications', 'shortcode' => '[eep_notifications]', 'view' => 'notifications'],
            [
                'slug' => $this->get_profile_route_slug('account', $profile_key),
                'title' => 'Account',
                'shortcode' => '[eep_account]',
                'view' => 'account',
            ],
            [
                'slug' => $this->get_profile_route_slug('auth', $profile_key),
                'title' => 'Login / Sign Up',
                'shortcode' => '[eep_auth]',
                'view' => 'auth',
            ],
            ['slug' => 'user-management', 'title' => 'User Management', 'shortcode' => '[eep_user_management]', 'view' => 'user-management'],
            ['slug' => 'moderation-queue', 'title' => 'Moderation Queue', 'shortcode' => '[eep_moderation_queue]', 'view' => 'moderation-queue'],
            ['slug' => 'organizers', 'title' => $owner_plural !== '' ? $owner_plural : __('Organizers', 'nexdirectory'), 'shortcode' => '[eep_organizers]', 'view' => 'organizers'],
            [
                'slug' => $this->get_profile_route_slug('claim', $profile_key),
                'title' => 'Claim Listing',
                'shortcode' => '[eep_claim_listing]',
                'view' => 'claim-listing',
            ],
        ];

        foreach ($pages as $page) {
            $slug = eep_sanitize_title((string) ($page['slug'] ?? ''));
            if ($slug === '') {
                continue;
            }
            $existing = get_page_by_path($slug);
            if ($existing instanceof WP_Post) {
                $existing_status = (string) get_post_status((int) $existing->ID);
                $expected_shortcode = trim((string) ($page['shortcode'] ?? ''));
                $existing_content = (string) $existing->post_content;
                $has_expected_shortcode = $expected_shortcode !== '' && stripos($existing_content, $expected_shortcode) !== false;
                $page_view = sanitize_key((string) ($page['view'] ?? ''));
                if ($page_view !== '') {
                    update_post_meta((int) $existing->ID, '_eep_is_app_page', '1');
                    update_post_meta((int) $existing->ID, '_eep_app_view', $page_view);
                }
                if ($has_expected_shortcode && $existing_status !== 'publish') {
                    wp_update_post([
                        'ID' => (int) $existing->ID,
                        'post_status' => 'publish',
                    ]);
                }
                continue;
            }

            $inserted_page_id = wp_insert_post([
                'post_type' => 'page',
                'post_status' => 'publish',
                'post_title' => $page['title'],
                'post_name' => $slug,
                'post_content' => $page['shortcode'],
            ]);
            if ($inserted_page_id > 0) {
                $page_view = sanitize_key((string) ($page['view'] ?? ''));
                if ($page_view !== '') {
                    update_post_meta((int) $inserted_page_id, '_eep_is_app_page', '1');
                    update_post_meta((int) $inserted_page_id, '_eep_app_view', $page_view);
                }
            }
        }
    }


    private function get_site_profile_catalog() {
        return [
            'events' => [
                'label' => __('Events (default)', 'nexdirectory'),
                'description' => __('Classic event discovery, map, RSVP, and calendar flows.', 'nexdirectory'),
                'singular' => __('Event', 'nexdirectory'),
                'plural' => __('Events', 'nexdirectory'),
                'owner_singular' => __('Organizer', 'nexdirectory'),
                'owner_plural' => __('Organizers', 'nexdirectory'),
                'classification_entity' => __('Event', 'nexdirectory'),
                'hero_kicker' => __('Event Event', 'nexdirectory'),
                'demo_profile' => 'events',
            ],
            'directory' => [
                'label' => __('Directory (Listings)', 'nexdirectory'),
                'description' => __('General-purpose listings directory mode for businesses, services, and organizations.', 'nexdirectory'),
                'singular' => __('Listing', 'nexdirectory'),
                'plural' => __('Listings', 'nexdirectory'),
                'owner_singular' => __('Owner', 'nexdirectory'),
                'owner_plural' => __('Owners', 'nexdirectory'),
                'classification_entity' => __('Listing', 'nexdirectory'),
                'hero_kicker' => __('Directory Listing', 'nexdirectory'),
                'demo_profile' => 'directory',
            ],
            'job_board' => [
                'label' => __('Job Board', 'nexdirectory'),
                'description' => __('Open role listings, employers, and recruitment workflows.', 'nexdirectory'),
                'singular' => __('Job', 'nexdirectory'),
                'plural' => __('Jobs', 'nexdirectory'),
                'owner_singular' => __('Employer', 'nexdirectory'),
                'owner_plural' => __('Employers', 'nexdirectory'),
                'classification_entity' => __('Job Listing', 'nexdirectory'),
                'hero_kicker' => __('Job Listing', 'nexdirectory'),
                'demo_profile' => 'job_board',
            ],
            'marketplace' => [
                'label' => __('Marketplace / Classifieds', 'nexdirectory'),
                'description' => __('Classified-style listings for products and local buying/selling.', 'nexdirectory'),
                'singular' => __('Listing', 'nexdirectory'),
                'plural' => __('Listings', 'nexdirectory'),
                'owner_singular' => __('Seller', 'nexdirectory'),
                'owner_plural' => __('Sellers', 'nexdirectory'),
                'classification_entity' => __('Marketplace Listing', 'nexdirectory'),
                'hero_kicker' => __('Marketplace Listing', 'nexdirectory'),
                'demo_profile' => 'marketplace',
            ],
        ];
    }


    private function normalize_site_profile_key($profile_key) {
        $profile_key = sanitize_key((string) $profile_key);
        if ($profile_key === '') {
            return 'events';
        }

        $map = [
            'events' => 'events',
            'event' => 'events',
            'directory' => 'directory',
            'directories' => 'directory',
            'listing' => 'directory',
            'listings' => 'directory',
            'directory-listings' => 'directory',
            'directory_listings' => 'directory',
            'job_board' => 'job_board',
            'job-board' => 'job_board',
            'jobboard' => 'job_board',
            'jobs' => 'job_board',
            'job' => 'job_board',
            'marketplace' => 'marketplace',
            'classifieds' => 'marketplace',
            'classified' => 'marketplace',
            'market-place' => 'marketplace',
            'classifieds_marketplace' => 'marketplace',
            'local_businesses' => 'directory',
            'trades_contractors' => 'directory',
            'professional_services' => 'directory',
            'clubs_community' => 'directory',
            'venues_directory' => 'directory',
            'property_agents' => 'directory',
            'health_wellness' => 'directory',
            'restaurants_hospitality' => 'directory',
            'education_training' => 'directory',
            'franchises_multilocation' => 'directory',
            'nonprofits_charities' => 'directory',
            'startup_tech' => 'directory',
            'tourism_attractions' => 'directory',
        ];

        if (isset($map[$profile_key])) {
            return (string) $map[$profile_key];
        }
        return $profile_key;
    }


    private function get_site_profile_key() {
        $catalog = $this->get_site_profile_catalog();
        $key = $this->normalize_site_profile_key((string) get_option('eep_site_profile', 'events'));
        if ($key === '' || !isset($catalog[$key])) {
            $key = 'events';
        }
        return $key;
    }


    private function get_site_profile_config($profile_key = '') {
        $catalog = $this->get_site_profile_catalog();
        $profile_key = $this->normalize_site_profile_key($profile_key);
        if ($profile_key === '' || !isset($catalog[$profile_key])) {
            $profile_key = $this->get_site_profile_key();
        }
        if (!isset($catalog[$profile_key])) {
            $profile_key = 'events';
        }
        $profile = isset($catalog[$profile_key]) && is_array($catalog[$profile_key]) ? $catalog[$profile_key] : [];
        $profile['key'] = $profile_key;
        $profile['singular'] = sanitize_text_field((string) ($profile['singular'] ?? __('Event', 'nexdirectory')));
        $profile['plural'] = sanitize_text_field((string) ($profile['plural'] ?? __('Events', 'nexdirectory')));
        $profile['owner_singular'] = sanitize_text_field((string) ($profile['owner_singular'] ?? __('Organizer', 'nexdirectory')));
        $profile['owner_plural'] = sanitize_text_field((string) ($profile['owner_plural'] ?? __('Organizers', 'nexdirectory')));
        $profile['classification_entity'] = sanitize_text_field((string) ($profile['classification_entity'] ?? $profile['singular']));
        $profile['hero_kicker'] = sanitize_text_field((string) ($profile['hero_kicker'] ?? ($this->get_white_label_brand_name() . ' ' . $profile['classification_entity'])));
        $profile['demo_profile'] = sanitize_key((string) ($profile['demo_profile'] ?? $profile_key));
        return $profile;
    }


    private function get_site_profile_ui_labels($profile_key = '') {
        $profile = $this->get_site_profile_config($profile_key);
        $singular = sanitize_text_field((string) ($profile['singular'] ?? __('Event', 'nexdirectory')));
        $plural = sanitize_text_field((string) ($profile['plural'] ?? __('Events', 'nexdirectory')));
        $owner_singular = sanitize_text_field((string) ($profile['owner_singular'] ?? __('Organizer', 'nexdirectory')));
        $owner_plural = sanitize_text_field((string) ($profile['owner_plural'] ?? __('Organizers', 'nexdirectory')));
        $classification_entity = sanitize_text_field((string) ($profile['classification_entity'] ?? $singular));
        $resolved_profile_key = sanitize_key((string) ($profile['key'] ?? 'events'));

        $singular_lower = function_exists('mb_strtolower') ? mb_strtolower($singular, 'UTF-8') : strtolower($singular);
        $plural_lower = function_exists('mb_strtolower') ? mb_strtolower($plural, 'UTF-8') : strtolower($plural);
        $is_events_profile = $resolved_profile_key === 'events';
        $is_job_profile = $resolved_profile_key === 'job_board';

        return [
            'profile_key' => $resolved_profile_key,
            'is_events_profile' => $is_events_profile ? '1' : '0',
            'is_job_profile' => $is_job_profile ? '1' : '0',
            'singular' => $singular,
            'plural' => $plural,
            'singular_lower' => $singular_lower,
            'plural_lower' => $plural_lower,
            'owner_singular' => $owner_singular,
            'owner_plural' => $owner_plural,
            'classification_entity' => $classification_entity,
            'hero_kicker_default' => sanitize_text_field((string) ($profile['hero_kicker'] ?? ($this->get_white_label_brand_name() . ' ' . $classification_entity))),
            'archive_title' => sprintf(__('View %s', 'nexdirectory'), $plural),
            'map_title' => $is_events_profile ? __('Map View', 'nexdirectory') : sprintf(__('%s Map', 'nexdirectory'), $singular),
            'map_description' => __('Explore events by location with live filters and search-this-area discovery.', 'nexdirectory'),
            'calendar_title' => sprintf(__('%s Calendar', 'nexdirectory'), $plural),
            'submit_add_title' => sprintf(__('Add %s', 'nexdirectory'), $singular),
            'my_items_title' => sprintf(__('My %s', 'nexdirectory'), $plural),
            'attending_title' => $is_events_profile ? __('Events I\'m Attending', 'nexdirectory') : sprintf(__('Saved %s', 'nexdirectory'), $plural),
            'owner_analytics_title' => __('Analytics', 'nexdirectory'),
            'taxonomy_type_singular' => $is_events_profile ? __('Event Type', 'nexdirectory') : sprintf(__('%s Type', 'nexdirectory'), $singular),
            'taxonomy_type_plural' => $is_events_profile ? __('Event Types', 'nexdirectory') : sprintf(__('%s Types', 'nexdirectory'), $singular),
            'taxonomy_mode_singular' => $is_events_profile ? __('Event Mode', 'nexdirectory') : __('Listing Mode', 'nexdirectory'),
            'taxonomy_mode_plural' => $is_events_profile ? __('Event Modes', 'nexdirectory') : __('Listing Modes', 'nexdirectory'),
            'taxonomy_tag_singular' => __('Tag', 'nexdirectory'),
            'taxonomy_tag_plural' => __('Tags', 'nexdirectory'),
            'filter_price_label' => $is_events_profile ? __('Price Range', 'nexdirectory') : __('Pricing', 'nexdirectory'),
            'filter_price_all_label' => $is_events_profile ? __('All Price Ranges', 'nexdirectory') : __('All Pricing', 'nexdirectory'),
            'map_near_me_label' => $is_events_profile ? __('Find Events Near Me', 'nexdirectory') : __('Find Near Me', 'nexdirectory'),
        ];
    }


    private function get_profile_route_slugs($route_key, $profile_key = '') {
        $route_key = sanitize_key((string) $route_key);
        $profile_key = $this->normalize_site_profile_key($profile_key);
        if ($profile_key === '') {
            $profile_key = $this->get_site_profile_key();
        }

        $preferred_map = [
            'events' => [
                'events' => 'events',
                'directory' => 'listings',
                'job_board' => 'jobs',
                'marketplace' => 'marketplace',
            ],
            'map' => [
                'events' => 'events-map',
                'directory' => 'listings-map',
                'job_board' => 'jobs-map',
                'marketplace' => 'marketplace-map',
            ],
            'calendar' => [
                'events' => 'events-calendar',
                'directory' => 'listings-calendar',
                'job_board' => 'jobs-calendar',
                'marketplace' => 'marketplace-calendar',
            ],
            'submit' => [
                'events' => 'submit',
                'directory' => 'submit',
                'job_board' => 'submit',
                'marketplace' => 'submit',
            ],
            'my_items' => [
                'events' => 'my-events',
                'directory' => 'my-listings',
                'job_board' => 'my-jobs',
                'marketplace' => 'my-items-selling',
            ],
            'boost' => [
                'events' => 'boost-post',
                'directory' => 'boost-post',
                'job_board' => 'boost-post',
                'marketplace' => 'boost-post',
            ],
            'analytics' => [
                'events' => 'owner-analytics',
                'directory' => 'owner-analytics',
                'job_board' => 'owner-analytics',
                'marketplace' => 'owner-analytics',
            ],
            'attending' => [
                'events' => 'rsvps',
                'directory' => 'saved-listings',
                'job_board' => 'saved-jobs',
                'marketplace' => 'saved-items',
            ],
            'attendees' => [
                'events' => 'whos-attending',
                'directory' => 'who-saved',
                'job_board' => 'who-applied',
                'marketplace' => 'who-saved',
            ],
            'account' => [
                'events' => 'account',
                'directory' => 'account',
                'job_board' => 'account',
                'marketplace' => 'account',
            ],
            'auth' => [
                'events' => 'login-signup',
                'directory' => 'login-signup',
                'job_board' => 'login-signup',
                'marketplace' => 'login-signup',
            ],
            'claim' => [
                'events' => 'claim-listing',
                'directory' => 'claim-listing',
                'job_board' => 'claim-listing',
                'marketplace' => 'claim-listing',
            ],
        ];

        $slugs = [];
        $preferred_slug = sanitize_title((string) ($preferred_map[$route_key][$profile_key] ?? ''));
        if ($preferred_slug !== '') {
            $slugs[] = $preferred_slug;
        }

        return array_values(array_unique($slugs));
    }


    private function get_profile_route_slug($route_key, $profile_key = '') {
        $slugs = $this->get_profile_route_slugs($route_key, $profile_key);
        return isset($slugs[0]) ? (string) $slugs[0] : '';
    }


    private function get_profile_route_default_url($route_key, $profile_key = '') {
        $slug = $this->get_profile_route_slug($route_key, $profile_key);
        if ($slug === '') {
            return home_url('/');
        }
        return home_url('/' . $slug . '/');
    }


    private function ensure_default_terms() {
        $ui_labels = method_exists($this, 'get_site_profile_ui_labels') ? (array) $this->get_site_profile_ui_labels() : [];
        $profile_key = sanitize_key((string) ($ui_labels['profile_key'] ?? 'events'));
        $is_events_profile = (string) ($ui_labels['is_events_profile'] ?? '1') === '1';
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));

        $topic_rows = method_exists($this, 'get_demo_profile_topics') ? (array) $this->get_demo_profile_topics($profile_key) : [];
        $type_terms = [];
        $mode_terms = [];
        $price_terms = [];
        $tag_terms = [];

        foreach ($topic_rows as $topic) {
            if (!is_array($topic)) {
                continue;
            }
            $type = sanitize_text_field((string) ($topic['type'] ?? ''));
            $mode = sanitize_text_field((string) ($topic['mode'] ?? ''));
            $price = sanitize_text_field((string) ($topic['price_range'] ?? ''));
            if ($type !== '') {
                $type_terms[] = $type;
            }
            if ($mode !== '') {
                $mode_terms[] = $mode;
            }
            if ($price !== '') {
                $price_terms[] = $price;
            }
            $topic_tags = isset($topic['tags']) ? $this->parse_delimited_values((string) $topic['tags']) : [];
            foreach ((array) $topic_tags as $topic_tag) {
                $topic_tag = sanitize_text_field((string) $topic_tag);
                if ($topic_tag !== '') {
                    $tag_terms[] = $topic_tag;
                }
            }
        }

        if (empty($type_terms)) {
            $type_terms = $is_events_profile
                ? ['Conference', 'Seminar', 'Workshop']
                : [$singular_label, __('Featured', 'nexdirectory'), __('Verified', 'nexdirectory')];
        }
        if (empty($mode_terms)) {
            $mode_terms = $is_events_profile
                ? ['In-person', 'Online Stream Event', 'Hybrid']
                : ['In-person', 'Online', 'Hybrid', 'Remote'];
        }
        if (empty($price_terms)) {
            $price_terms = $is_events_profile
                ? ['Free', 'Low Cost', 'Paid', 'Premium']
                : ['Free', 'Paid', 'Premium', 'Quote Required'];
        }
        if (empty($tag_terms)) {
            $tag_terms = $is_events_profile
                ? ['Community', 'Featured']
                : ['Featured', 'Verified', 'Popular'];
        }

        $insert_terms = static function ($taxonomy, $terms) {
            $terms = array_values(array_unique(array_filter(array_map('eep_sanitize_text', (array) $terms), static function ($term) {
                return trim((string) $term) !== '';
            })));
            foreach ($terms as $term) {
                if (!term_exists($term, $taxonomy)) {
                    wp_insert_term($term, $taxonomy);
                }
            }
        };

        $insert_terms(self::TAX_TYPE, $type_terms);
        $insert_terms(self::TAX_MODE, $mode_terms);
        $insert_terms(self::TAX_PRICE, $price_terms);
        $insert_terms(self::TAX_TAG, $tag_terms);
    }


    public function register_query_vars() {
        // Placeholder action to keep init hook explicit for URL integrations.
    }


    public function add_query_vars($vars) {
        $vars[] = 'eep_ics';
        return $vars;
    }


    public function register_custom_cron_schedules($schedules) {
        if (!isset($schedules['eep_hourly'])) {
            $schedules['eep_hourly'] = [
                'interval' => HOUR_IN_SECONDS,
                'display' => __('Every Hour (EEP)', 'nexdirectory'),
            ];
        }
        $chatbot_hours = max(1, min(168, (int) get_option('eep_chatbot_reindex_interval_hours', 24)));
        $schedules['eep_chatbot_interval'] = [
            'interval' => $chatbot_hours * HOUR_IN_SECONDS,
            'display' => sprintf(
                __('Every %d hour(s) (EEP Chatbot)', 'nexdirectory'),
                $chatbot_hours
            ),
        ];

        return $schedules;
    }


    private function schedule_reminders() {
        if ($this->should_use_action_scheduler()) {
            if (function_exists('as_unschedule_all_actions')) {
                as_unschedule_all_actions('eep_event_reminder_cron', [], 'eep');
            }
            if (function_exists('as_next_scheduled_action') && function_exists('as_schedule_recurring_action')) {
                $next_action = as_next_scheduled_action('eep_event_reminder_cron', [], 'eep');
                if (!$next_action) {
                    as_schedule_recurring_action(time() + 300, HOUR_IN_SECONDS, 'eep_event_reminder_cron', [], 'eep');
                }
            }
            if (wp_next_scheduled('eep_event_reminder_cron')) {
                wp_clear_scheduled_hook('eep_event_reminder_cron');
            }
            $this->schedule_chatbot_reindex();
            return;
        }

        if (function_exists('as_unschedule_all_actions')) {
            as_unschedule_all_actions('eep_event_reminder_cron', [], 'eep');
        }
        $scheduled = wp_get_scheduled_event('eep_event_reminder_cron');
        // Migrate legacy custom schedule to WP core hourly once.
        if ($scheduled && (string) ($scheduled->schedule ?? '') !== 'hourly') {
            wp_clear_scheduled_hook('eep_event_reminder_cron');
            $scheduled = false;
        }
        if (!$scheduled) {
            wp_schedule_event(time() + 300, 'hourly', 'eep_event_reminder_cron');
        }
        $this->schedule_chatbot_reindex();
    }


    private function schedule_chatbot_reindex() {
        $enabled = (string) get_option('eep_chatbot_enabled', '0') === '1';
        $interval_seconds = max(1, min(168, (int) get_option('eep_chatbot_reindex_interval_hours', 24))) * HOUR_IN_SECONDS;

        if ($this->should_use_action_scheduler()) {
            if (function_exists('as_unschedule_all_actions')) {
                as_unschedule_all_actions('eep_chatbot_reindex_cron', [], 'eep');
            }
            if ($enabled && function_exists('as_schedule_recurring_action')) {
                as_schedule_recurring_action(time() + 420, $interval_seconds, 'eep_chatbot_reindex_cron', [], 'eep');
            }
            if (wp_next_scheduled('eep_chatbot_reindex_cron')) {
                wp_clear_scheduled_hook('eep_chatbot_reindex_cron');
            }
            return;
        }

        if (function_exists('as_unschedule_all_actions')) {
            as_unschedule_all_actions('eep_chatbot_reindex_cron', [], 'eep');
        }
        if (!$enabled) {
            wp_clear_scheduled_hook('eep_chatbot_reindex_cron');
            return;
        }
        $scheduled = wp_get_scheduled_event('eep_chatbot_reindex_cron');
        if ($scheduled && (int) ($scheduled->interval ?? 0) !== $interval_seconds) {
            wp_clear_scheduled_hook('eep_chatbot_reindex_cron');
            $scheduled = null;
        }
        if (!$scheduled) {
            wp_schedule_event(time() + 420, 'eep_chatbot_interval', 'eep_chatbot_reindex_cron');
        }
    }


    private function should_use_action_scheduler() {
        return (string) get_option('eep_use_action_scheduler', '0') === '1'
            && function_exists('as_schedule_single_action')
            && function_exists('as_next_scheduled_action');
    }


    public function enqueue_assets() {
        $design = $this->get_platform_design_settings();
        if (!is_array($design)) {
            $design = [];
        }
        $design = wp_parse_args($design, [
            'use_defaults' => false,
            'accent' => '#FF5E14',
            'hover' => '#E04F0D',
            'on_primary' => '#FFFFFF',
            'font_body' => 'Inter, system-ui, -apple-system, sans-serif',
            'font_display' => 'Oswald, Inter, sans-serif',
            'font_size_base' => '18px',
            'h2_size' => '1.75rem',
            'h3_size' => '1.35rem',
            'weight_body' => '400',
            'weight_heading' => '700',
            'space_unit' => '16px',
            'space_scale' => '1',
            'radius_md' => '12px',
            'radius_lg' => '24px',
            'box_style' => 'default',
            'button_style' => 'solid',
            'button_radius_mode' => 'inherit',
            'density_mode' => 'comfortable',
            'border_mode' => 'subtle',
            'shadow_mode' => 'soft',
            'content_width' => 'standard',
            'input_style' => 'outline',
        ]);
        $tailwind_path = plugin_dir_path(EEP_PLUGIN_FILE) . 'assets/css/eep-tailwind.css';
        $frontend_css_path = plugin_dir_path(EEP_PLUGIN_FILE) . 'assets/css/eep-frontend.css';
        $frontend_js_path = plugin_dir_path(EEP_PLUGIN_FILE) . 'assets/js/eep-frontend.js';
        $tailwind_ver = is_readable($tailwind_path) ? self::VERSION . '.' . (string) filemtime($tailwind_path) : self::VERSION;
        $frontend_css_ver = is_readable($frontend_css_path) ? self::VERSION . '.' . (string) filemtime($frontend_css_path) : self::VERSION;
        $frontend_js_ver = is_readable($frontend_js_path) ? self::VERSION . '.' . (string) filemtime($frontend_js_path) : self::VERSION;
        $is_map_shortcode_context = false;
        $map_shortcode_tags = ['eep_events_map', 'eep_listings_map', 'nexdirectory_events_map', 'nexdirectory_listings_map'];
        if (is_singular()) {
            $current_post = get_queried_object();
            if ($current_post instanceof WP_Post) {
                $current_content = (string) $current_post->post_content;
                if ($current_content !== '') {
                    foreach ($map_shortcode_tags as $shortcode_tag) {
                        if (has_shortcode($current_content, $shortcode_tag)) {
                            $is_map_shortcode_context = true;
                            break;
                        }
                    }
                }
            }
        }
        if (!$is_map_shortcode_context && method_exists($this, 'get_platform_page_url') && method_exists($this, 'get_profile_route_slugs') && method_exists($this, 'get_profile_route_default_url')) {
            $map_page_url = $this->get_platform_page_url(
                'eep_page_events_map_url',
                $this->get_profile_route_slugs('map'),
                $this->get_profile_route_default_url('map')
            );
            if ($map_page_url !== '') {
                $map_page_id = absint((int) url_to_postid($map_page_url));
                if ($map_page_id > 0 && is_page($map_page_id)) {
                    $is_map_shortcode_context = true;
                }
            }
        }

        if (empty($design['use_defaults']) && !$is_map_shortcode_context) {
            wp_register_style(
                'eep-fonts',
                'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Oswald:wght@500;700&display=swap',
                [],
                null
            );
            wp_enqueue_style('eep-fonts');
        }

        wp_register_style(
            'eep-tailwind',
            plugin_dir_url(EEP_PLUGIN_FILE) . 'assets/css/eep-tailwind.css',
            [],
            $tailwind_ver
        );
        if (!$is_map_shortcode_context) {
            wp_enqueue_style('eep-tailwind');
        }

        wp_register_script(
            'eep-tailwind-play',
            'https://cdn.tailwindcss.com',
            [],
            null,
            false
        );
        wp_add_inline_script(
            'eep-tailwind-play',
            'window.tailwind=window.tailwind||{};window.tailwind.config={corePlugins:{preflight:false},theme:{extend:{fontFamily:{sans:["Inter","sans-serif"]},colors:{brand:{50:"color-mix(in srgb, var(--eep-primary) 8%, #ffffff)",100:"color-mix(in srgb, var(--eep-primary) 14%, #ffffff)",200:"color-mix(in srgb, var(--eep-primary) 24%, #ffffff)",300:"color-mix(in srgb, var(--eep-primary) 36%, #ffffff)",400:"color-mix(in srgb, var(--eep-primary) 52%, #ffffff)",500:"var(--eep-primary)",600:"var(--eep-primary-hover)",700:"color-mix(in srgb, var(--eep-primary-hover) 80%, #000000)",800:"color-mix(in srgb, var(--eep-primary-hover) 64%, #000000)",900:"color-mix(in srgb, var(--eep-primary-hover) 48%, #000000)"}}}}};',
            'before'
        );
        wp_enqueue_script('eep-tailwind-play');

        wp_register_script(
            'eep-alpine',
            'https://cdn.jsdelivr.net/npm/alpinejs@3.14.1/dist/cdn.min.js',
            [],
            '3.14.1',
            true
        );
        wp_script_add_data('eep-alpine', 'defer', true);
        wp_add_inline_script(
            'eep-alpine',
            'window.__eepDeferredAlpineInit = null; window.deferLoadingAlpine = function (init) { window.__eepDeferredAlpineInit = init; };',
            'before'
        );
        wp_enqueue_script('eep-alpine');

        wp_register_script(
            'eep-lenis',
            'https://unpkg.com/lenis@1.1.18/dist/lenis.min.js',
            [],
            '1.1.18',
            true
        );
        wp_script_add_data('eep-lenis', 'defer', true);
        wp_enqueue_script('eep-lenis');

        wp_register_style(
            'eep-leaflet',
            'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css',
            [],
            '1.9.4'
        );

        wp_register_script(
            'eep-leaflet',
            'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js',
            [],
            '1.9.4',
            true
        );
        wp_register_style(
            'eep-leaflet-markercluster',
            'https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css',
            ['eep-leaflet'],
            '1.5.3'
        );
        wp_register_style(
            'eep-leaflet-markercluster-default',
            'https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css',
            ['eep-leaflet-markercluster'],
            '1.5.3'
        );
        wp_register_script(
            'eep-leaflet-markercluster',
            'https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js',
            ['eep-leaflet'],
            '1.5.3',
            true
        );
        wp_enqueue_style('eep-leaflet');
        wp_enqueue_style('eep-leaflet-markercluster-default');
        wp_enqueue_script('eep-leaflet');
        wp_enqueue_script('eep-leaflet-markercluster');

        wp_register_style(
            'eep-frontend',
            plugin_dir_url(EEP_PLUGIN_FILE) . 'assets/css/eep-frontend.css',
            [],
            $frontend_css_ver
        );

        wp_register_script(
            'eep-frontend',
            plugin_dir_url(EEP_PLUGIN_FILE) . 'assets/js/eep-frontend.js',
            ['jquery', 'eep-alpine', 'eep-lenis'],
            $frontend_js_ver,
            true
        );

        if (!$is_map_shortcode_context) {
            wp_enqueue_style('eep-frontend');
        }
        $clean_css_value = static function ($value) {
            return str_replace(['{', '}', ';'], '', (string) $value);
        };
        $accent_hex = sanitize_hex_color((string) ($design['accent'] ?? '')) ?: '#FF5E14';
        $hover_hex = sanitize_hex_color((string) ($design['hover'] ?? '')) ?: '#E04F0D';
        $on_primary_hex = sanitize_hex_color((string) ($design['on_primary'] ?? '')) ?: '#FFFFFF';
        $accent_rgb = [255, 94, 20];
        if (preg_match('/^#?([a-f0-9]{6})$/i', $accent_hex, $accent_match) === 1) {
            $accent_value = ltrim((string) $accent_match[1], '#');
            $accent_rgb = [
                (int) hexdec(substr($accent_value, 0, 2)),
                (int) hexdec(substr($accent_value, 2, 2)),
                (int) hexdec(substr($accent_value, 4, 2)),
            ];
        }
        $page_side_padding_desktop = $this->sanitize_page_side_padding_option((string) get_option('eep_page_side_padding', '10%'));
        $page_side_padding_mobile = $this->sanitize_page_side_padding_mobile_option((string) get_option('eep_page_side_padding_mobile', '2%'));
        if ($page_side_padding_mobile === '') {
            $page_side_padding_mobile = $page_side_padding_desktop;
        }
        $box_style = sanitize_key((string) ($design['box_style'] ?? 'default'));
        if (!in_array($box_style, ['default', 'gradient', 'bold', 'neumorphic'], true)) {
            $box_style = 'default';
        }
        $button_style = sanitize_key((string) ($design['button_style'] ?? 'solid'));
        if (!in_array($button_style, ['solid', 'outline', 'soft', 'dark'], true)) {
            $button_style = 'solid';
        }
        $button_radius_mode = sanitize_key((string) ($design['button_radius_mode'] ?? 'inherit'));
        if (!in_array($button_radius_mode, ['inherit', 'pill', 'sharp'], true)) {
            $button_radius_mode = 'inherit';
        }
        $density_mode = sanitize_key((string) ($design['density_mode'] ?? 'comfortable'));
        if (!in_array($density_mode, ['compact', 'comfortable', 'loose'], true)) {
            $density_mode = 'comfortable';
        }
        $border_mode = sanitize_key((string) ($design['border_mode'] ?? 'subtle'));
        if (!in_array($border_mode, ['minimal', 'subtle', 'strong'], true)) {
            $border_mode = 'subtle';
        }
        $shadow_mode = sanitize_key((string) ($design['shadow_mode'] ?? 'soft'));
        if (!in_array($shadow_mode, ['none', 'soft', 'medium', 'dramatic'], true)) {
            $shadow_mode = 'soft';
        }
        $content_width = sanitize_key((string) ($design['content_width'] ?? 'standard'));
        if (!in_array($content_width, ['narrow', 'standard', 'wide', 'full'], true)) {
            $content_width = 'standard';
        }
        $input_style = sanitize_key((string) ($design['input_style'] ?? 'outline'));
        if (!in_array($input_style, ['outline', 'filled', 'underlined'], true)) {
            $input_style = 'outline';
        }

        $content_width_max = '1220px';
        $event_page_max_width = '1240px';
        $archive_map_max_width = '1400px';
        if ($content_width === 'narrow') {
            $content_width_max = '1040px';
            $event_page_max_width = '1080px';
            $archive_map_max_width = '1180px';
        } elseif ($content_width === 'wide') {
            $content_width_max = '1380px';
            $event_page_max_width = '1440px';
            $archive_map_max_width = '1600px';
        } elseif ($content_width === 'full') {
            $content_width_max = '100%';
            $event_page_max_width = '100%';
            $archive_map_max_width = '100%';
        }

        $density_scale = '1';
        $button_min_height = '40px';
        $field_pad_y = '10px';
        $field_pad_x = '12px';
        if ($density_mode === 'compact') {
            $density_scale = '0.88';
            $button_min_height = '36px';
            $field_pad_y = '8px';
            $field_pad_x = '10px';
        } elseif ($density_mode === 'loose') {
            $density_scale = '1.18';
            $button_min_height = '44px';
            $field_pad_y = '12px';
            $field_pad_x = '14px';
        }

        $box_background = '#ffffff';
        $box_background_alt = '#f8fafc';
        if ($box_style === 'gradient') {
            $box_background = 'linear-gradient(145deg, #fff7ed 0%, #ffffff 48%, #fffbeb 100%)';
            $box_background_alt = '#fffaf5';
        } elseif ($box_style === 'bold') {
            $box_background = 'color-mix(in srgb, var(--eep-primary) 10%, #ffffff)';
            $box_background_alt = 'color-mix(in srgb, var(--eep-primary) 6%, #ffffff)';
        } elseif ($box_style === 'neumorphic') {
            $box_background = '#f3f4f6';
            $box_background_alt = '#eef2f7';
        }

        $border_width = '1px';
        $border_color = '#e5e7eb';
        if ($border_mode === 'minimal') {
            $border_color = 'rgba(148, 163, 184, 0.26)';
        } elseif ($border_mode === 'strong') {
            $border_width = '2px';
            $border_color = '#cbd5e1';
        }

        $shadow_sm = '0 6px 16px rgba(0, 0, 0, 0.04)';
        $shadow_md = '0 12px 30px rgba(11, 18, 33, 0.08)';
        $shadow_lg = '0 22px 45px rgba(11, 18, 33, 0.14)';
        if ($shadow_mode === 'none') {
            $shadow_sm = 'none';
            $shadow_md = 'none';
            $shadow_lg = 'none';
        } elseif ($shadow_mode === 'medium') {
            $shadow_sm = '0 8px 18px rgba(11, 18, 33, 0.09)';
            $shadow_md = '0 14px 34px rgba(11, 18, 33, 0.14)';
            $shadow_lg = '0 24px 50px rgba(11, 18, 33, 0.2)';
        } elseif ($shadow_mode === 'dramatic') {
            $shadow_sm = '0 10px 24px rgba(11, 18, 33, 0.14)';
            $shadow_md = '0 20px 44px rgba(11, 18, 33, 0.2)';
            $shadow_lg = '0 30px 70px rgba(11, 18, 33, 0.3)';
        }

        if ($box_style === 'neumorphic') {
            $shadow_sm = '6px 6px 14px rgba(148, 163, 184, 0.25), -6px -6px 14px rgba(255, 255, 255, 0.95)';
            $shadow_md = '10px 10px 24px rgba(148, 163, 184, 0.27), -10px -10px 24px rgba(255, 255, 255, 0.96)';
            $shadow_lg = '14px 14px 32px rgba(148, 163, 184, 0.28), -14px -14px 32px rgba(255, 255, 255, 0.96)';
            if ($shadow_mode === 'none') {
                $shadow_sm = 'none';
                $shadow_md = 'none';
                $shadow_lg = 'none';
            }
            $border_color = 'transparent';
        }

        $btn_bg = 'var(--eep-primary)';
        $btn_bg_hover = 'var(--eep-primary-hover)';
        $btn_color = 'var(--eep-on-primary, #ffffff)';
        $btn_border = '1px solid transparent';
        $btn_letter_spacing = '.06em';
        $btn_transform = 'uppercase';
        $btn_weight = '700';
        if ($button_style === 'outline') {
            $btn_bg = 'transparent';
            $btn_bg_hover = 'color-mix(in srgb, var(--eep-primary) 10%, #ffffff)';
            $btn_color = 'var(--eep-primary)';
            $btn_border = '2px solid var(--eep-primary)';
            $btn_letter_spacing = '.03em';
            $btn_transform = 'none';
        } elseif ($button_style === 'soft') {
            $btn_bg = 'color-mix(in srgb, var(--eep-primary) 16%, #ffffff)';
            $btn_bg_hover = 'color-mix(in srgb, var(--eep-primary) 24%, #ffffff)';
            $btn_color = 'color-mix(in srgb, var(--eep-primary-hover) 66%, #111827)';
            $btn_border = '1px solid color-mix(in srgb, var(--eep-primary) 26%, #ffffff)';
            $btn_letter_spacing = '.02em';
            $btn_transform = 'none';
        } elseif ($button_style === 'dark') {
            $btn_bg = '#0f172a';
            $btn_bg_hover = '#111827';
            $btn_color = '#ffffff';
            $btn_border = '1px solid #0f172a';
            $btn_letter_spacing = '.03em';
            $btn_transform = 'none';
        }

        $btn_radius = 'var(--eep-radius-pill)';
        if ($button_radius_mode === 'pill') {
            $btn_radius = '999px';
        } elseif ($button_radius_mode === 'sharp') {
            $btn_radius = '6px';
        }

        $input_bg = '#ffffff';
        $input_border = '#e5e7eb';
        $input_border_focus = 'var(--eep-primary)';
        $input_border_width = '1px';
        $input_radius = 'var(--eep-radius)';
        if ($input_style === 'filled') {
            $input_bg = '#f8fafc';
            $input_border = '#e2e8f0';
        } elseif ($input_style === 'underlined') {
            $input_bg = 'transparent';
            $input_border = 'transparent transparent #cbd5e1 transparent';
            $input_border_focus = 'transparent transparent var(--eep-primary) transparent';
            $input_border_width = '0 0 2px 0';
            $input_radius = '0';
        }

        $dynamic_css = ':root{'
            . '--eep-primary:' . $clean_css_value($accent_hex) . ';'
            . '--eep-primary-hover:' . $clean_css_value($hover_hex) . ';'
            . '--eep-on-primary:' . $clean_css_value($on_primary_hex) . ';'
            . '--eep-font:' . $clean_css_value($design['font_body']) . ';'
            . '--eep-font-display:' . $clean_css_value($design['font_display']) . ';'
            . '--eep-base-font-size:' . $clean_css_value($design['font_size_base']) . ';'
            . '--eep-h2-size:' . $clean_css_value($design['h2_size']) . ';'
            . '--eep-h3-size:' . $clean_css_value($design['h3_size']) . ';'
            . '--eep-font-weight-body:' . $clean_css_value($design['weight_body']) . ';'
            . '--eep-font-weight-heading:' . $clean_css_value($design['weight_heading']) . ';'
            . '--eep-space-unit:' . $clean_css_value($design['space_unit']) . ';'
            . '--eep-space-scale:' . $clean_css_value($design['space_scale']) . ';'
            . '--eep-radius:' . $clean_css_value($design['radius_md']) . ';'
            . '--eep-radius-lg:' . $clean_css_value($design['radius_lg']) . ';'
            . '--eep-page-side-padding:' . $clean_css_value($page_side_padding_desktop) . ';'
            . '--eep-page-side-padding-mobile:' . $clean_css_value($page_side_padding_mobile) . ';'
            . '--eep-content-max-width:' . $clean_css_value($content_width_max) . ';'
            . '--eep-event-page-max-width:' . $clean_css_value($event_page_max_width) . ';'
            . '--eep-archive-max-width:' . $clean_css_value($archive_map_max_width) . ';'
            . '--eep-density-scale:' . $clean_css_value($density_scale) . ';'
            . '--eep-field-pad-y:' . $clean_css_value($field_pad_y) . ';'
            . '--eep-field-pad-x:' . $clean_css_value($field_pad_x) . ';'
            . '--eep-btn-min-height:' . $clean_css_value($button_min_height) . ';'
            . '--eep-box-bg:' . $clean_css_value($box_background) . ';'
            . '--eep-box-bg-alt:' . $clean_css_value($box_background_alt) . ';'
            . '--eep-box-border-color:' . $clean_css_value($border_color) . ';'
            . '--eep-box-border-width:' . $clean_css_value($border_width) . ';'
            . '--eep-shadow-sm:' . $clean_css_value($shadow_sm) . ';'
            . '--eep-shadow:' . $clean_css_value($shadow_md) . ';'
            . '--eep-shadow-lg:' . $clean_css_value($shadow_lg) . ';'
            . '--eep-btn-bg:' . $clean_css_value($btn_bg) . ';'
            . '--eep-btn-bg-hover:' . $clean_css_value($btn_bg_hover) . ';'
            . '--eep-btn-color:' . $clean_css_value($btn_color) . ';'
            . '--eep-btn-border:' . $clean_css_value($btn_border) . ';'
            . '--eep-btn-letter-spacing:' . $clean_css_value($btn_letter_spacing) . ';'
            . '--eep-btn-transform:' . $clean_css_value($btn_transform) . ';'
            . '--eep-btn-font-weight:' . $clean_css_value($btn_weight) . ';'
            . '--eep-btn-radius:' . $clean_css_value($btn_radius) . ';'
            . '--eep-input-bg:' . $clean_css_value($input_bg) . ';'
            . '--eep-input-border:' . $clean_css_value($input_border) . ';'
            . '--eep-input-border-focus:' . $clean_css_value($input_border_focus) . ';'
            . '--eep-input-border-width:' . $clean_css_value($input_border_width) . ';'
            . '--eep-input-radius:' . $clean_css_value($input_radius) . ';'
            . '--eep-primary-rgb:' . implode(', ', $accent_rgb) . ';'
            . '}';
        $dynamic_css .= '@media (max-width: 767px){:root{--eep-page-side-padding:var(--eep-page-side-padding-mobile);}}';
        if (!empty($design['use_defaults'])) {
            $dynamic_css .= '.eep-theme-shell,.eep-layout,.eep-submit-wrap,.eep-auth-grid,.eep-my-events,.eep-form{font-family:inherit!important;}';
        } else {
            $font_scope = '.eep-theme-shell,.eep-layout,.eep-submit-wrap,.eep-auth-grid,.eep-my-events,.eep-form,.eep-account-shell,.eep-platform-nav,#eep-map-default-wrapper,#eep-map-triple-wrapper';
            $dynamic_css .= $font_scope . '{font-family:var(--eep-font)!important;}';
            $dynamic_css .= $font_scope . ' :is(p,span,a,li,ul,ol,label,small,strong,em,b,button,input,select,textarea,th,td,caption,figcaption,blockquote,dt,dd,summary,details){font-family:var(--eep-font)!important;}';
            $dynamic_css .= $font_scope . ' :is(h1,h2,h3,h4,h5,h6,.eep-event-hero-title,.eep-map-item-title){font-family:var(--eep-font-display)!important;}';
        }
        wp_register_style('eep-runtime-vars', false, [], $frontend_css_ver);
        wp_enqueue_style('eep-runtime-vars');
        wp_add_inline_style('eep-runtime-vars', $dynamic_css);
        wp_enqueue_script('eep-frontend');
        wp_add_inline_script(
            'eep-frontend',
            '(function(){var tries=0;function boot(){if(typeof window.__eepDeferredAlpineInit==="function"){window.__eepDeferredAlpineInit();window.__eepDeferredAlpineInit=null;return true;}return false;}if(!boot()){var timer=window.setInterval(function(){tries++;if(boot()||tries>120){window.clearInterval(timer);}},50);}})();',
            'after'
        );
        if (is_user_logged_in()) {
            wp_enqueue_media();
        }
        $upload_limits = $this->get_upload_limits();
        $excerpt_max_chars = max(80, min(1000, (int) get_option('eep_excerpt_max_chars', 220)));
        $ui_labels = method_exists($this, 'get_site_profile_ui_labels') ? (array) $this->get_site_profile_ui_labels() : [];
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $plural_label = sanitize_text_field((string) ($ui_labels['plural'] ?? __('Events', 'nexdirectory')));
        $singular_lower = sanitize_text_field((string) ($ui_labels['singular_lower'] ?? strtolower((string) $singular_label)));
        $plural_lower = sanitize_text_field((string) ($ui_labels['plural_lower'] ?? strtolower((string) $plural_label)));
        $owner_plural = sanitize_text_field((string) ($ui_labels['owner_plural'] ?? __('Organizers', 'nexdirectory')));
        $spa_route_urls = method_exists($this, 'get_spa_hybrid_route_urls') ? (array) $this->get_spa_hybrid_route_urls() : [];
        $spa_route_urls = array_values(array_unique(array_filter(array_map('esc_url_raw', $spa_route_urls))));
        wp_localize_script('eep-frontend', 'eepConfig', [
            'googleMapsApiKey' => (string) get_option('eep_google_maps_api_key', ''),
            'mapPinUrl' => (string) get_option('eep_map_pin_url', ''),
            'recaptchaSiteKey' => (string) get_option('eep_recaptcha_site_key', ''),
            'recaptchaVersion' => (string) get_option('eep_recaptcha_version', 'v3'),
            'recaptchaShowSubmit' => $this->is_option_enabled('eep_recaptcha_show_submit', '0'),
            'recaptchaBypassSubmitForPrivileged' => (is_user_logged_in() && current_user_can('manage_options')),
            'autosaveEndpoint' => admin_url('admin-post.php'),
            'autosaveNonce' => wp_create_nonce('eep_autosave_event_nonce'),
            'uploadEndpoint' => admin_url('admin-post.php'),
            'uploadNonce' => wp_create_nonce('eep_upload_image_nonce'),
            'uploadCoverMaxBytes' => (int) ($upload_limits['cover_max_bytes'] ?? 512000),
            'uploadGalleryMaxBytes' => (int) ($upload_limits['gallery_max_bytes'] ?? 2048000),
            'uploadGalleryMaxFiles' => (int) ($upload_limits['gallery_max_files'] ?? 10),
            'uploadFileMaxBytes' => (int) ($upload_limits['file_max_bytes'] ?? 2097152),
            'excerptMaxChars' => $excerpt_max_chars,
            'enableSubmitAutosave' => $this->is_option_enabled('eep_enable_submit_autosave', '1'),
            'smoothScrolling' => $this->is_option_enabled('eep_enable_smooth_scrolling', '0'),
            'smoothScrollProfile' => method_exists($this, 'sanitize_smooth_scroll_profile_option')
                ? (string) $this->sanitize_smooth_scroll_profile_option((string) get_option('eep_smooth_scroll_profile', 'balanced'))
                : (string) get_option('eep_smooth_scroll_profile', 'balanced'),
            'smoothScrollTouchpadBehavior' => method_exists($this, 'sanitize_smooth_scroll_touchpad_behavior_option')
                ? (string) $this->sanitize_smooth_scroll_touchpad_behavior_option((string) get_option('eep_smooth_scroll_touchpad_behavior', 'native'))
                : (string) get_option('eep_smooth_scroll_touchpad_behavior', 'native'),
            'smoothScrollWheelMultiplier' => method_exists($this, 'sanitize_smooth_scroll_wheel_multiplier_option')
                ? (float) $this->sanitize_smooth_scroll_wheel_multiplier_option((string) get_option('eep_smooth_scroll_wheel_multiplier', '1'))
                : (float) get_option('eep_smooth_scroll_wheel_multiplier', '1'),
            'smoothScrollEase' => method_exists($this, 'sanitize_smooth_scroll_ease_option')
                ? (float) $this->sanitize_smooth_scroll_ease_option((string) get_option('eep_smooth_scroll_ease', '1.2'))
                : (float) get_option('eep_smooth_scroll_ease', '1.2'),
            'smoothScrollMaxStepPx' => method_exists($this, 'sanitize_smooth_scroll_max_step_option')
                ? (int) $this->sanitize_smooth_scroll_max_step_option((int) get_option('eep_smooth_scroll_max_step_px', 960))
                : (int) get_option('eep_smooth_scroll_max_step_px', 960),
            'smoothScrollMinStepPx' => method_exists($this, 'sanitize_smooth_scroll_min_step_option')
                ? (float) $this->sanitize_smooth_scroll_min_step_option((string) get_option('eep_smooth_scroll_min_step_px', '0.16'))
                : (float) get_option('eep_smooth_scroll_min_step_px', '0.16'),
            'smoothScrollEnableOnTouch' => $this->is_option_enabled('eep_smooth_scroll_enable_on_touch', '0'),
            'preloaderEnabled' => $this->is_option_enabled('eep_preloader_enabled', '1'),
            'preloaderSource' => method_exists($this, 'sanitize_preloader_source_option')
                ? (string) $this->sanitize_preloader_source_option((string) get_option('eep_preloader_source', 'svg'))
                : (string) get_option('eep_preloader_source', 'svg'),
            'preloaderSvg' => method_exists($this, 'sanitize_preloader_svg_option')
                ? (string) $this->sanitize_preloader_svg_option((string) get_option('eep_preloader_svg_markup', method_exists($this, 'get_default_preloader_svg_markup') ? (string) $this->get_default_preloader_svg_markup() : ''))
                : (string) get_option('eep_preloader_svg_markup', ''),
            'preloaderImageUrl' => esc_url_raw((string) get_option('eep_preloader_image_url', '')),
            'preloaderBackgroundColor' => method_exists($this, 'sanitize_preloader_background_color_option')
                ? (string) $this->sanitize_preloader_background_color_option((string) get_option('eep_preloader_background_color', '#FFFFFF'))
                : (string) get_option('eep_preloader_background_color', '#FFFFFF'),
            'preloaderLogoMaxWidth' => method_exists($this, 'sanitize_preloader_logo_max_width_option')
                ? (int) $this->sanitize_preloader_logo_max_width_option((int) get_option('eep_preloader_logo_max_width', 420))
                : (int) get_option('eep_preloader_logo_max_width', 420),
            'preloaderMinDurationMs' => method_exists($this, 'sanitize_preloader_min_duration_option')
                ? (int) $this->sanitize_preloader_min_duration_option((int) get_option('eep_preloader_min_duration_ms', 400))
                : (int) get_option('eep_preloader_min_duration_ms', 400),
            'preloaderFadeDurationMs' => method_exists($this, 'sanitize_preloader_fade_duration_option')
                ? (int) $this->sanitize_preloader_fade_duration_option((int) get_option('eep_preloader_fade_duration_ms', 420))
                : (int) get_option('eep_preloader_fade_duration_ms', 420),
            'preloaderShowOnInitial' => $this->is_option_enabled('eep_preloader_show_on_initial', '1'),
            'preloaderShowOnSpa' => $this->is_option_enabled('eep_preloader_show_on_spa', '1'),
            'preloaderShowOnlyHome' => $this->is_option_enabled('eep_preloader_show_only_home', '0'),
            'preloaderShowOnce' => $this->is_option_enabled('eep_preloader_show_once', '0'),
            'preloaderHomeUrl' => esc_url_raw(home_url('/')),
            'preloaderIsHomePage' => (bool) (is_front_page() || is_home()),
            'preloaderSeenStorageKey' => 'eep_preloader_seen_' . md5((string) home_url('/')),
            'preloaderDisableOnMobile' => $this->is_option_enabled('eep_preloader_disable_mobile', '0'),
            'preloaderRespectReducedMotion' => $this->is_option_enabled('eep_preloader_respect_motion', '1'),
            'autosaveInterval' => (int) get_option('eep_autosave_interval_seconds', 60),
            'mapRestUrl' => esc_url_raw(rest_url('eep/v1/map-events')),
            'geocodeRestUrl' => esc_url_raw(rest_url('eep/v1/geocode')),
            'addressSuggestRestUrl' => esc_url_raw(rest_url('eep/v1/address-suggestions')),
            'archiveRestUrl' => esc_url_raw(rest_url('eep/v1/archive-events')),
            'quickSearchRestUrl' => esc_url_raw(rest_url('eep/v1/quick-search')),
            'analyticsTrackUrl' => esc_url_raw(rest_url('eep/v1/analytics-track')),
            'userSearchRestUrl' => esc_url_raw(rest_url('eep/v1/user-search')),
            'submitRestUrl' => esc_url_raw(rest_url('eep/v1/event-submit')),
            'rsvpRestUrl' => esc_url_raw(rest_url('eep/v1/rsvp')),
            'saveFiltersRestUrl' => esc_url_raw(rest_url('eep/v1/save-filters')),
            'inboxPollRestUrl' => esc_url_raw(rest_url('eep/v1/inbox-poll')),
            'moderationRestUrl' => esc_url_raw(rest_url('eep/v1/moderate-event')),
            'moderationBulkRestUrl' => esc_url_raw(rest_url('eep/v1/moderate-events-bulk')),
            'inboxPageUrl' => esc_url_raw((string) $this->get_notifications_page_url()),
            'eventsPageUrl' => esc_url_raw((string) $this->get_platform_page_url(
                'eep_page_events_url',
                $this->get_profile_route_slugs('events'),
                $this->get_profile_route_default_url('events')
            )),
            'mapPageUrl' => esc_url_raw((string) $this->get_platform_page_url(
                'eep_page_events_map_url',
                $this->get_profile_route_slugs('map'),
                $this->get_profile_route_default_url('map')
            )),
            'spaEnabled' => method_exists($this, 'is_spa_hybrid_enabled') ? (bool) $this->is_spa_hybrid_enabled() : true,
            'spaFeedbackBadge' => (string) get_option('eep_show_spa_feedback_badge', '0') === '1',
            'spaSelector' => '[data-eep-spa-main="1"]',
            'spaRoutes' => $spa_route_urls,
            'enableModerationAjax' => (string) get_option('eep_enable_moderation_ajax', '1') === '1',
            'restNonce' => wp_create_nonce('wp_rest'),
            'currentUserId' => get_current_user_id(),
            'mapAutoSearch' => (string) get_option('eep_enable_map_auto_search', '1') === '1',
            'mapResultsPerPage' => (int) get_option('eep_map_results_per_page', 120),
            'mapProvider' => (string) get_option('eep_map_provider', 'openstreetmap'),
            'mapStyle' => (string) get_option('eep_map_style', 'osm-standard'),
            'mapFocusRegion' => method_exists($this, 'sanitize_map_focus_region_option')
                ? (string) $this->sanitize_map_focus_region_option((string) get_option('eep_map_focus_region', 'global'))
                : (string) get_option('eep_map_focus_region', 'global'),
            'mapboxAccessToken' => (string) get_option('eep_mapbox_access_token', ''),
            'mapboxStyleId' => method_exists($this, 'sanitize_mapbox_style_id_option')
                ? (string) $this->sanitize_mapbox_style_id_option((string) get_option('eep_mapbox_style_id', 'mapbox/streets-v12'))
                : (string) get_option('eep_mapbox_style_id', 'mapbox/streets-v12'),
            'mapCustomTileUrl' => (string) get_option('eep_map_custom_tile_url', ''),
            'mapCustomTileAttribution' => (string) get_option('eep_map_custom_tile_attribution', ''),
            'mapCustomTileSubdomains' => (string) get_option('eep_map_custom_tile_subdomains', 'a,b,c'),
            'fallbackThumbUrl' => esc_url_raw((string) $this->get_default_image_url('thumbnail')),
            'fallbackHeroUrl' => esc_url_raw((string) $this->get_default_image_url('hero')),
            'enableHeroParallax' => (string) get_option('eep_enable_hero_parallax', '1') === '1',
            'chatbotEnabled' => (string) get_option('eep_chatbot_enabled', '0') === '1',
            'chatbotDebugMode' => (string) get_option('eep_chatbot_debug_mode', '0') === '1',
            'chatbotRestUrl' => esc_url_raw(rest_url('eep/v1/chatbot-query')),
            'chatbotName' => sanitize_text_field((string) get_option('eep_chatbot_name', __('Listing Assistant', 'nexdirectory'))),
            'chatbotWelcomeText' => sanitize_text_field((string) get_option('eep_chatbot_welcome_text', __('Ask me about upcoming listings and I will recommend the best matches.', 'nexdirectory'))),
            'chatbotPosition' => sanitize_key((string) get_option('eep_chatbot_position_desktop', 'bottom-right')),
            'chatbotPositionDesktop' => sanitize_key((string) get_option('eep_chatbot_position_desktop', 'bottom-right')),
            'chatbotPositionMobile' => sanitize_key((string) get_option('eep_chatbot_position_mobile', 'right-edge-center')),
            'chatbotThemeColor' => sanitize_hex_color((string) get_option('eep_chatbot_theme_color', '#FF5E14')) ?: '#FF5E14',
            'chatbotTextColor' => sanitize_hex_color((string) get_option('eep_chatbot_text_color', '#0B1221')) ?: '#0B1221',
            'messages' => [
                'noMapResults' => sprintf(__('No matching %s found.', 'nexdirectory'), $plural_lower),
                'enterAddress' => __('Enter an address first.', 'nexdirectory'),
                'geocodeNoResult' => __('No location found for that address.', 'nexdirectory'),
                'geocodeFailed' => __('Could not locate that address right now.', 'nexdirectory'),
                'recaptchaLoadFailed' => __('reCAPTCHA failed to load.', 'nexdirectory'),
                'recaptchaRequired' => __('Please complete the reCAPTCHA challenge before submitting.', 'nexdirectory'),
                'mapLoadFailed' => sprintf(__('Could not load map %s.', 'nexdirectory'), $plural_lower),
                'mapLoading' => sprintf(__('Loading %s...', 'nexdirectory'), $plural_lower),
                'mapLocateUnsupported' => __('Your browser does not support location services.', 'nexdirectory'),
                'mapLocateDenied' => __('Could not access your location.', 'nexdirectory'),
                'mapLocateInProgress' => __('Finding your location...', 'nexdirectory'),
                'mapNearMeLabel' => sanitize_text_field((string) ($ui_labels['map_near_me_label'] ?? __('Find Near Me', 'nexdirectory'))),
                'mapLoadMore' => __('Load more results', 'nexdirectory'),
                'mapLoadMoreDone' => __('No more results', 'nexdirectory'),
                'mapAriaLabel' => sprintf(__('%s locations map', 'nexdirectory'), $plural_label),
                'addressSuggestFailed' => __('Could not load address suggestions right now.', 'nexdirectory'),
                'archiveLoading' => sprintf(__('Updating %s...', 'nexdirectory'), $plural_lower),
                'archiveLoadFailed' => sprintf(__('Could not update %s list.', 'nexdirectory'), $plural_lower),
                'archiveFiltersSaved' => __('Filters saved on this device.', 'nexdirectory'),
                'archiveNoSavedFilters' => __('No saved filters found on this device.', 'nexdirectory'),
                'archiveNoFiltersToSave' => __('Apply at least one filter before saving.', 'nexdirectory'),
                'archiveSaveFailed' => __('Could not save filters on this device.', 'nexdirectory'),
                'quickSearchPlaceholder' => sprintf(__('Search %1$s, locations, and %2$s...', 'nexdirectory'), $plural_lower, strtolower((string) $owner_plural)),
                'quickSearchNoResults' => __('No matches yet. Try different keywords.', 'nexdirectory'),
                'quickSearchLabelDate' => __('Date', 'nexdirectory'),
                'quickSearchLabelLocation' => __('Location', 'nexdirectory'),
                'viewDetails' => sprintf(__('View %s', 'nexdirectory'), $singular_label),
                'boostBadgeLabel' => sanitize_text_field((string) get_option('eep_boost_featured_badge_text', __('Featured', 'nexdirectory'))),
                'verifiedLabel' => __('Verified', 'nexdirectory'),
                'calendarEvents' => $plural_label,
                'calendarNoEvents' => sprintf(__('No %s found for this day.', 'nexdirectory'), $plural_lower),
                'calendarUntitled' => sprintf(__('Untitled %s', 'nexdirectory'), $singular_lower),
                'userSearchFailed' => __('Could not search users right now.', 'nexdirectory'),
                'pastDateWarning' => __('Start date is in the past. You can still submit if this is intentional.', 'nexdirectory'),
                'pastDateConfirm' => __('The start date is in the past. Are you sure you want to continue?', 'nexdirectory'),
                'pastDateCancelled' => __('Submission cancelled. Adjust the date or submit again to confirm.', 'nexdirectory'),
                'restActionFailed' => __('Request failed. Please try again.', 'nexdirectory'),
                'spaLoading' => __('Loading page...', 'nexdirectory'),
                'spaLoadFailed' => __('Could not load that page. Opening full page view.', 'nexdirectory'),
                'chatbotAskPlaceholder' => __('Ask about listings, dates, locations, tags, or recommendations...', 'nexdirectory'),
                'chatbotEmptyPrompt' => __('Type a question first.', 'nexdirectory'),
                'chatbotTyping' => __('Thinking…', 'nexdirectory'),
                'chatbotError' => __('The assistant is unavailable right now. Please try again in a moment.', 'nexdirectory'),
                'inboxToastTitle' => __('New Inbox Message', 'nexdirectory'),
                'inboxToastSubtitle' => __('Open your inbox to view and reply.', 'nexdirectory'),
                'inboxToastBody' => __('You have a new message in your inbox.', 'nexdirectory'),
            ],
        ]);
    }


    public function register_rest_routes() {
        register_rest_route('eep/v1', '/map-events', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'rest_get_map_events'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/archive-events', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'rest_get_archive_events'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/quick-search', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'rest_quick_search'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/widget-search', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'rest_widget_search'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/analytics-track', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_track_event_analytics'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/geocode', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'rest_geocode_address'],
            'permission_callback' => [$this, 'rest_geocode_permission'],
        ]);
        register_rest_route('eep/v1', '/address-suggestions', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'rest_address_suggestions'],
            'permission_callback' => [$this, 'rest_geocode_permission'],
        ]);
        register_rest_route('eep/v1', '/user-search', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'rest_search_users'],
            'permission_callback' => [$this, 'rest_user_search_permission'],
        ]);
        register_rest_route('eep/v1', '/rsvp', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_rsvp_event'],
            'permission_callback' => [$this, 'rest_authenticated_permission'],
        ]);
        register_rest_route('eep/v1', '/event-submit', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_submit_event'],
            'permission_callback' => [$this, 'rest_user_submission_permission'],
        ]);
        register_rest_route('eep/v1', '/save-filters', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_save_filters'],
            'permission_callback' => [$this, 'rest_authenticated_permission'],
        ]);
        register_rest_route('eep/v1', '/inbox-poll', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'rest_inbox_poll'],
            'permission_callback' => [$this, 'rest_authenticated_permission'],
        ]);
        register_rest_route('eep/v1', '/chatbot-query', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_chatbot_query'],
            'permission_callback' => [$this, 'rest_chatbot_permission'],
        ]);
        register_rest_route('eep/v1', '/event-webapp-bootstrap', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'rest_event_webapp_bootstrap'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/event-webapp-chat', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'rest_event_webapp_chat'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/event-webapp-chat', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_event_webapp_chat'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/event-webapp-dm', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'rest_event_webapp_dm'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/event-webapp-dm', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_event_webapp_dm'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/event-webapp-qa', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'rest_event_webapp_qa'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/event-webapp-qa', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_event_webapp_qa'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/event-webapp-polls', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'rest_event_webapp_polls'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/event-webapp-polls', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_event_webapp_polls'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/event-webapp-notifications', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'rest_event_webapp_notifications'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/event-webapp-notifications', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_event_webapp_notifications'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/event-webapp-itinerary', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'rest_event_webapp_itinerary'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/event-webapp-itinerary', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_event_webapp_itinerary'],
            'permission_callback' => [$this, 'rest_public_events_permission'],
        ]);
        register_rest_route('eep/v1', '/moderate-event', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_moderate_event'],
            'permission_callback' => [$this, 'rest_moderation_permission'],
        ]);
        register_rest_route('eep/v1', '/moderate-events-bulk', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_moderate_events_bulk'],
            'permission_callback' => [$this, 'rest_moderation_permission'],
        ]);
        register_rest_route('eep/v1', '/extension/ping', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_extension_ping'],
            'permission_callback' => [$this, 'rest_extension_permission'],
        ]);
        register_rest_route('eep/v1', '/extension/import', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_extension_import_event'],
            'permission_callback' => [$this, 'rest_extension_permission'],
        ]);
        register_rest_route('eep/v1', '/extension/preferences', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'rest_extension_update_preferences'],
            'permission_callback' => [$this, 'rest_extension_permission'],
        ]);
    }


    public function rest_geocode_permission() {
        return is_user_logged_in() && $this->user_can_submit_events();
    }


    public function rest_public_events_permission() {
        return true;
    }


    public function rest_track_event_analytics($request) {
        $event_id = absint((int) $request->get_param('event_id'));
        if ($event_id <= 0 || get_post_type($event_id) !== self::POST_TYPE) {
            return new WP_Error('eep_invalid_event', __('Invalid event selected.', 'nexdirectory'), ['status' => 400]);
        }
        $post_status = (string) get_post_status($event_id);
        if ($post_status !== 'publish' && !current_user_can('edit_post', $event_id)) {
            return new WP_Error('eep_event_unavailable', __('This listing is not publicly available.', 'nexdirectory'), ['status' => 403]);
        }

        $metric = sanitize_key((string) $request->get_param('metric'));
        $allowed_metrics = ['click_phone', 'click_website', 'click_whatsapp'];
        if (!in_array($metric, $allowed_metrics, true)) {
            return new WP_Error('eep_invalid_metric', __('Invalid analytics metric.', 'nexdirectory'), ['status' => 400]);
        }

        $source = sanitize_key((string) $request->get_param('source'));
        $allowed_sources = [
            'single',
            'map',
            'archive',
            'calendar',
            'account',
            'my_items',
            'owner_analytics',
            'chatbot',
        ];
        if (!in_array($source, $allowed_sources, true)) {
            $source = 'single';
        }
        $identity_seed = is_user_logged_in()
            ? 'u:' . (string) get_current_user_id()
            : 'ip:' . sanitize_text_field((string) ($_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'));
        $throttle_key = 'eep_track_' . md5($metric . '|' . (string) $event_id . '|' . $identity_seed);
        if (get_transient($throttle_key)) {
            return rest_ensure_response([
                'ok' => true,
                'recorded' => false,
                'reason' => 'throttled',
            ]);
        }
        set_transient($throttle_key, '1', 15);

        $recorded = $this->record_analytics_event($event_id, $metric, [
            'context' => $source,
            'user_id' => is_user_logged_in() ? get_current_user_id() : 0,
        ]);

        return rest_ensure_response([
            'ok' => true,
            'recorded' => (bool) $recorded,
        ]);
    }


    private function build_geocode_query_from_parts($address = '', $city = '', $country = '', $venue = '') {
        $parts = array_filter(array_map('trim', [
            sanitize_text_field((string) $venue),
            sanitize_text_field((string) $address),
            sanitize_text_field((string) $city),
            sanitize_text_field((string) $country),
        ]), static function ($value) {
            return $value !== '';
        });
        if (empty($parts)) {
            return '';
        }
        $unique = [];
        foreach ($parts as $part) {
            $key = strtolower((string) $part);
            if (!isset($unique[$key])) {
                $unique[$key] = $part;
            }
        }
        return trim((string) implode(', ', array_values($unique)));
    }


    private function geocode_event_location_from_parts($address = '', $city = '', $country = '', $venue = '') {
        $query = $this->build_geocode_query_from_parts($address, $city, $country, $venue);
        if ($query === '') {
            return null;
        }

        $headers = [
            'Accept' => 'application/json',
            'User-Agent' => 'EventPluginWP/' . self::VERSION . ' (' . home_url('/') . ')',
        ];
        $google_key = (string) get_option('eep_google_maps_api_key', '');
        if ($google_key !== '') {
            $google_url = eep_add_query_arg([
                'address' => $query,
                'key' => $google_key,
            ], 'https://maps.googleapis.com/maps/api/geocode/json');
            $google_response = wp_remote_get($google_url, [
                'timeout' => 12,
                'headers' => $headers,
            ]);
            if (!is_wp_error($google_response)) {
                $payload = json_decode((string) wp_remote_retrieve_body($google_response), true);
                $row = is_array($payload) && !empty($payload['results'][0]) ? (array) $payload['results'][0] : [];
                $location = !empty($row['geometry']['location']) && is_array($row['geometry']['location']) ? (array) $row['geometry']['location'] : [];
                if (isset($location['lat'], $location['lng']) && is_numeric($location['lat']) && is_numeric($location['lng'])) {
                    $components = !empty($row['address_components']) && is_array($row['address_components']) ? (array) $row['address_components'] : [];
                    $city_value = '';
                    $country_value = '';
                    foreach ($components as $component) {
                        if (!is_array($component) || empty($component['types']) || empty($component['long_name'])) {
                            continue;
                        }
                        $types = (array) $component['types'];
                        if ($city_value === '' && (in_array('locality', $types, true) || in_array('postal_town', $types, true) || in_array('administrative_area_level_2', $types, true))) {
                            $city_value = sanitize_text_field((string) $component['long_name']);
                        }
                        if ($country_value === '' && in_array('country', $types, true)) {
                            $country_value = sanitize_text_field((string) $component['long_name']);
                        }
                    }
                    return [
                        'lat' => (float) $location['lat'],
                        'lng' => (float) $location['lng'],
                        'city' => $city_value,
                        'country' => $country_value,
                        'address' => sanitize_text_field((string) ($row['formatted_address'] ?? $query)),
                    ];
                }
            }
        }

        $osm_url = eep_add_query_arg([
            'format' => 'json',
            'addressdetails' => 1,
            'limit' => 1,
            'q' => $query,
        ], 'https://nominatim.openstreetmap.org/search');
        $osm_response = wp_remote_get($osm_url, [
            'timeout' => 12,
            'headers' => $headers,
        ]);
        if (is_wp_error($osm_response)) {
            return null;
        }
        $payload = json_decode((string) wp_remote_retrieve_body($osm_response), true);
        if (!is_array($payload) || empty($payload[0]['lat']) || empty($payload[0]['lon'])) {
            return null;
        }
        $first = (array) $payload[0];
        $address_bits = isset($first['address']) && is_array($first['address']) ? (array) $first['address'] : [];
        $city_value = '';
        foreach (['city', 'town', 'village', 'municipality', 'county', 'state_district', 'state'] as $city_key) {
            if (!empty($address_bits[$city_key])) {
                $city_value = sanitize_text_field((string) $address_bits[$city_key]);
                break;
            }
        }
        $country_value = !empty($address_bits['country']) ? sanitize_text_field((string) $address_bits['country']) : '';
        return [
            'lat' => (float) $payload[0]['lat'],
            'lng' => (float) $payload[0]['lon'],
            'city' => $city_value,
            'country' => $country_value,
            'address' => sanitize_text_field((string) ($first['display_name'] ?? $query)),
        ];
    }


    private function maybe_backfill_missing_event_coordinates($limit = 8) {
        $limit = max(1, min(60, (int) $limit));
        $lock_key = 'eep_map_geo_backfill_lock';
        $next_key = 'eep_map_geo_backfill_next';
        if ((int) get_transient($lock_key) === 1) {
            return;
        }
        $next_allowed = (int) get_transient($next_key);
        if ($next_allowed > time()) {
            return;
        }

        set_transient($lock_key, 1, 30);
        $updated = 0;
        try {
            $query = new WP_Query([
                'post_type' => self::POST_TYPE,
                'post_status' => ['publish', 'pending', 'draft', 'future'],
                'fields' => 'ids',
                'posts_per_page' => $limit,
                'orderby' => 'modified',
                'order' => 'DESC',
                'no_found_rows' => true,
                'meta_query' => [
                    'relation' => 'OR',
                    [
                        'key' => 'eep_lat',
                        'compare' => 'NOT EXISTS',
                    ],
                    [
                        'key' => 'eep_lat',
                        'value' => '',
                        'compare' => '=',
                    ],
                    [
                        'key' => 'eep_lng',
                        'compare' => 'NOT EXISTS',
                    ],
                    [
                        'key' => 'eep_lng',
                        'value' => '',
                        'compare' => '=',
                    ],
                    [
                        'key' => 'eep_lat',
                        'value' => ',',
                        'compare' => 'LIKE',
                    ],
                    [
                        'key' => 'eep_lng',
                        'value' => ',',
                        'compare' => 'LIKE',
                    ],
                ],
            ]);
            $ids = is_array($query->posts) ? array_map('absint', $query->posts) : [];
            foreach ($ids as $post_id) {
                if ($post_id <= 0) {
                    continue;
                }
                $lat_existing = (string) get_post_meta($post_id, 'eep_lat', true);
                $lng_existing = (string) get_post_meta($post_id, 'eep_lng', true);
                $lat_normalized = str_replace(',', '.', trim($lat_existing));
                $lng_normalized = str_replace(',', '.', trim($lng_existing));
                if (is_numeric($lat_normalized) && is_numeric($lng_normalized)) {
                    if ($lat_normalized !== trim($lat_existing)) {
                        update_post_meta($post_id, 'eep_lat', (string) ((float) $lat_normalized));
                    }
                    if ($lng_normalized !== trim($lng_existing)) {
                        update_post_meta($post_id, 'eep_lng', (string) ((float) $lng_normalized));
                    }
                    continue;
                }
                $address = (string) get_post_meta($post_id, 'eep_address', true);
                $city = (string) get_post_meta($post_id, 'eep_city', true);
                $country = (string) get_post_meta($post_id, 'eep_country', true);
                $venue = (string) get_post_meta($post_id, 'eep_venue', true);
                $geo = $this->geocode_event_location_from_parts($address, $city, $country, $venue);
                if (!is_array($geo) || !isset($geo['lat'], $geo['lng']) || !is_numeric($geo['lat']) || !is_numeric($geo['lng'])) {
                    continue;
                }
                update_post_meta($post_id, 'eep_lat', (string) $geo['lat']);
                update_post_meta($post_id, 'eep_lng', (string) $geo['lng']);
                if (trim($city) === '' && !empty($geo['city'])) {
                    update_post_meta($post_id, 'eep_city', sanitize_text_field((string) $geo['city']));
                }
                if (trim($country) === '' && !empty($geo['country'])) {
                    update_post_meta($post_id, 'eep_country', sanitize_text_field((string) $geo['country']));
                }
                if (trim($address) === '' && !empty($geo['address'])) {
                    update_post_meta($post_id, 'eep_address', sanitize_text_field((string) $geo['address']));
                }
                $updated++;
            }
        } finally {
            delete_transient($lock_key);
            set_transient($next_key, time() + ($updated > 0 ? 120 : 300), ($updated > 0 ? 120 : 300));
        }
    }


    private function haversine_distance_km($lat1, $lng1, $lat2, $lng2) {
        $earth_radius = 6371.0;
        $lat_delta = deg2rad((float) $lat2 - (float) $lat1);
        $lng_delta = deg2rad((float) $lng2 - (float) $lng1);
        $a = sin($lat_delta / 2) * sin($lat_delta / 2)
            + cos(deg2rad((float) $lat1)) * cos(deg2rad((float) $lat2))
            * sin($lng_delta / 2) * sin($lng_delta / 2);
        $c = 2 * atan2(sqrt($a), sqrt(1 - $a));
        return $earth_radius * $c;
    }


    private function get_rsvp_table_name() {
        global $wpdb;
        return $wpdb->prefix . self::RSVP_TABLE;
    }


    private function get_event_attendees_by_status($post_id, $status) {
        global $wpdb;
        $table = $this->get_rsvp_table_name();
        $status = sanitize_key((string) $status);
        if (!in_array($status, ['yes', 'waitlist'], true)) {
            $status = 'yes';
        }
        $rows = $wpdb->get_col($wpdb->prepare(
            "SELECT user_id FROM {$table} WHERE post_id = %d AND status = %s ORDER BY id ASC",
            (int) $post_id,
            $status
        ));
        if (!is_array($rows)) {
            return [];
        }
        return array_values(array_unique(array_map('intval', $rows)));
    }


    private function sync_rsvp_meta_from_table($post_id) {
        update_post_meta($post_id, 'eep_rsvp_yes', $this->get_event_attendees_by_status($post_id, 'yes'));
        update_post_meta($post_id, 'eep_waitlist', $this->get_event_attendees_by_status($post_id, 'waitlist'));
    }


    private function build_archive_query($filters, $posts_per_page, $paged, $organizer_id = 0) {
        $ui_labels = method_exists($this, 'get_site_profile_ui_labels') ? (array) $this->get_site_profile_ui_labels() : [];
        $profile_key = sanitize_key((string) ($ui_labels['profile_key'] ?? 'events'));
        $is_events_profile = $profile_key === 'events';
        $is_job_profile = $profile_key === 'job_board';
        $type = sanitize_text_field((string) ($filters['event_type'] ?? ''));
        $mode = sanitize_text_field((string) ($filters['event_mode'] ?? ''));
        $price_range = sanitize_text_field((string) ($filters['event_price'] ?? ''));
        $tag = sanitize_text_field((string) ($filters['event_tag'] ?? ''));
        $city = sanitize_text_field((string) ($filters['city'] ?? ''));
        $date_from = sanitize_text_field((string) ($filters['date_from'] ?? ''));
        $official = sanitize_text_field((string) ($filters['official'] ?? ''));
        $search = sanitize_text_field((string) ($filters['s'] ?? ''));
        if ($is_job_profile) {
            $price_range = '';
        }
        if (!$is_events_profile) {
            $official = '';
        }

        $tax_query = [];
        if ($type !== '') {
            $tax_query[] = [
                'taxonomy' => self::TAX_TYPE,
                'field' => 'slug',
                'terms' => $type,
            ];
        }
        if ($mode !== '') {
            $tax_query[] = [
                'taxonomy' => self::TAX_MODE,
                'field' => 'slug',
                'terms' => $mode,
            ];
        }
        if ($price_range !== '') {
            $tax_query[] = [
                'taxonomy' => self::TAX_PRICE,
                'field' => 'slug',
                'terms' => $price_range,
            ];
        }
        if ($tag !== '') {
            $tax_query[] = [
                'taxonomy' => self::TAX_TAG,
                'field' => 'slug',
                'terms' => $tag,
            ];
        }

        $meta_query = [];
        if ($city !== '') {
            $meta_query[] = [
                'key' => 'eep_city',
                'value' => $city,
                'compare' => 'LIKE',
            ];
        }
        if ($date_from !== '') {
            $meta_query[] = [
                'key' => 'eep_start',
                'value' => $date_from,
                'compare' => '>=',
                'type' => 'CHAR',
            ];
        }
        if (in_array($official, ['0', '1'], true)) {
            $meta_query[] = [
                'key' => 'eep_official_event',
                'value' => $official,
                'compare' => '=',
            ];
        }

        $args = [
            'post_type' => self::POST_TYPE,
            'post_status' => 'publish',
            'posts_per_page' => max(1, (int) $posts_per_page),
            'paged' => max(1, (int) $paged),
            's' => $search,
            'meta_key' => 'eep_start',
            'orderby' => 'meta_value',
            'order' => 'ASC',
            'eep_apply_boost_order' => 1,
        ];
        if ($organizer_id > 0) {
            $args['author'] = (int) $organizer_id;
        }
        if (!empty($tax_query)) {
            $args['tax_query'] = $tax_query;
        }
        if (!empty($meta_query)) {
            $args['meta_query'] = $meta_query;
        }
        return new WP_Query($args);
    }


    private function render_archive_results_markup($query, $paged, $filters, $organizer_id = 0) {
        $ui_labels = method_exists($this, 'get_site_profile_ui_labels') ? (array) $this->get_site_profile_ui_labels() : [];
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $plural_label = sanitize_text_field((string) ($ui_labels['plural'] ?? __('Events', 'nexdirectory')));
        $singular_lower = sanitize_text_field((string) ($ui_labels['singular_lower'] ?? strtolower((string) $singular_label)));
        $plural_lower = sanitize_text_field((string) ($ui_labels['plural_lower'] ?? strtolower((string) $plural_label)));
        $profile_key = sanitize_key((string) ($ui_labels['profile_key'] ?? 'events'));
        $is_events_profile = $profile_key === 'events';
        $is_job_profile = $profile_key === 'job_board';
        $is_directory_profile = $profile_key === 'directory';
        $is_marketplace_profile = $profile_key === 'marketplace';
        ob_start();
        ?>
        <div class="eep-grid">
            <?php if ($query->have_posts()) : ?>
                <?php while ($query->have_posts()) : $query->the_post(); ?>
                    <?php
                    $post_id = get_the_ID();
                    $card_date_label = $this->format_event_date($post_id);
                    $secondary_meta_label = '';
                    if ($is_job_profile) {
                        $closing_raw = trim((string) get_post_meta($post_id, 'eep_job_closing_date', true));
                        $closing_ts = $closing_raw !== '' ? strtotime($closing_raw . ' 23:59:00') : false;
                        if ($closing_ts) {
                            $card_date_label = sprintf(
                                /* translators: %s: date */
                                __('Closes %s', 'nexdirectory'),
                                wp_date(get_option('date_format'), (int) $closing_ts)
                            );
                        }
                        $salary = trim((string) get_post_meta($post_id, 'eep_job_salary', true));
                        if ($salary !== '') {
                            $secondary_meta_label = $salary;
                        } else {
                            $company = trim((string) get_post_meta($post_id, 'eep_company_name', true));
                            if ($company !== '') {
                                $secondary_meta_label = $company;
                            }
                        }
                    } elseif ($is_directory_profile) {
                        $updated_ts = strtotime((string) get_post_field('post_modified', $post_id));
                        if ($updated_ts) {
                            $card_date_label = sprintf(
                                /* translators: %s: date */
                                __('Updated %s', 'nexdirectory'),
                                wp_date(get_option('date_format'), (int) $updated_ts)
                            );
                        }
                        $type_terms = wp_get_post_terms($post_id, self::TAX_TYPE, ['fields' => 'names']);
                        if (!is_wp_error($type_terms) && !empty($type_terms[0])) {
                            $secondary_meta_label = sanitize_text_field((string) $type_terms[0]);
                        }
                    } elseif ($is_marketplace_profile) {
                        $listed_ts = strtotime((string) get_post_field('post_date', $post_id));
                        if ($listed_ts) {
                            $card_date_label = sprintf(
                                /* translators: %s: date */
                                __('Listed %s', 'nexdirectory'),
                                wp_date(get_option('date_format'), (int) $listed_ts)
                            );
                        }
                        $condition = trim((string) get_post_meta($post_id, 'eep_market_condition', true));
                        $price = trim((string) get_post_meta($post_id, 'eep_price', true));
                        if ($condition !== '') {
                            $secondary_meta_label = $condition;
                        } elseif ($price !== '') {
                            $secondary_meta_label = $price;
                        }
                    }
                    $card_location = trim((string) get_post_meta($post_id, 'eep_city', true));
                    if ($card_location === '') {
                        $card_location = trim((string) get_post_meta($post_id, 'eep_venue', true));
                    }
                    if ($card_location === '') {
                        $card_location = trim((string) get_post_meta($post_id, 'eep_country', true));
                    }
                    $boost_badge = method_exists($this, 'get_boost_badge_config') ? (array) $this->get_boost_badge_config() : [];
                    $is_boosted = method_exists($this, 'is_listing_boosted') ? (bool) $this->is_listing_boosted($post_id) : false;
                    $show_featured_badge = $is_boosted && !empty($boost_badge['show']);
                    $featured_badge_text = sanitize_text_field((string) ($boost_badge['text'] ?? __('Featured', 'nexdirectory')));
                    if ($featured_badge_text === '') {
                        $featured_badge_text = __('Featured', 'nexdirectory');
                    }
                    $featured_badge_image = esc_url((string) ($boost_badge['image'] ?? ''));
                    $apply_boost_highlight = $is_boosted && !empty($boost_badge['highlight']);
                    $show_verified_badge = !empty($boost_badge['verified_badge']) && get_user_meta((int) get_post_field('post_author', $post_id), 'eep_verified_organizer', true) === '1';
                    $article_classes = 'eep-card eep-archive-event-card';
                    if ($apply_boost_highlight) {
                        $article_classes .= ' eep-card-boosted';
                    }
                    if ($show_verified_badge) {
                        $article_classes .= ' eep-card-verified';
                    }
                    ?>
                    <article class="<?php echo esc_attr($article_classes); ?>" data-eep-card-url="<?php echo esc_url(get_permalink($post_id)); ?>" tabindex="0" role="link" aria-label="<?php echo esc_attr(sprintf(__('View %1$s: %2$s', 'nexdirectory'), $singular_lower, get_the_title($post_id))); ?>">
                        <?php $cover = $this->get_event_cover_image_url($post_id, 'medium_large', 'thumbnail'); ?>
                        <div class="eep-card-image-wrap">
                            <?php if ($show_featured_badge) : ?>
                                <span class="eep-boost-badge" aria-label="<?php echo esc_attr($featured_badge_text); ?>">
                                    <?php if ($featured_badge_image !== '') : ?>
                                        <img src="<?php echo esc_url($featured_badge_image); ?>" alt="<?php echo esc_attr($featured_badge_text); ?>" loading="lazy" decoding="async">
                                    <?php else : ?>
                                        <?php echo esc_html($featured_badge_text); ?>
                                    <?php endif; ?>
                                </span>
                            <?php endif; ?>
                            <?php if ($show_verified_badge) : ?>
                                <span class="eep-verified-badge"><?php esc_html_e('Verified', 'nexdirectory'); ?></span>
                            <?php endif; ?>
                            <?php if ($cover) : ?>
                                <a class="eep-card-cover-link" href="<?php echo esc_url(get_permalink($post_id)); ?>" aria-label="<?php echo esc_attr(get_the_title($post_id)); ?>">
                                    <img class="eep-card-cover" src="<?php echo esc_url($cover); ?>" alt="<?php echo esc_attr(get_the_title($post_id)); ?>" loading="lazy" decoding="async" fetchpriority="low">
                                </a>
                            <?php else : ?>
                                <div class="eep-card-cover eep-card-cover-fallback">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
                                </div>
                            <?php endif; ?>
                        </div>

                        <div class="eep-archive-card-content">
                            <h4 class="eep-card-title"><a href="<?php echo esc_url(get_permalink($post_id)); ?>"><?php echo esc_html(get_the_title($post_id)); ?></a></h4>

                            <div class="eep-meta eep-card-meta-row">
                                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
                                <span><?php echo esc_html((string) $card_date_label); ?></span>
                            </div>

                            <?php if ($secondary_meta_label !== '') : ?>
                                <div class="eep-meta eep-card-meta-row">
                                    <span><?php echo esc_html($secondary_meta_label); ?></span>
                                </div>
                            <?php endif; ?>
                            <?php if ($card_location !== '') : ?>
                                <div class="eep-meta eep-card-meta-row">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
                                    <span><?php echo esc_html($card_location); ?></span>
                                </div>
                            <?php endif; ?>

                            <div class="eep-archive-event-card-footer">
                                <a class="eep-btn eep-btn-secondary eep-archive-view-btn" href="<?php echo esc_url(get_permalink($post_id)); ?>">
                                    <?php echo esc_html(sprintf(__('View %s', 'nexdirectory'), $singular_label)); ?>
                                </a>
                            </div>
                        </div>
                    </article>
                <?php endwhile; ?>
            <?php else : ?>
                <div class="eep-empty-state eep-archive-empty-state">
                    <h4><?php echo esc_html(sprintf(__('No %s found', 'nexdirectory'), $plural_lower)); ?></h4>
                    <p><?php echo esc_html(sprintf(__('Try broadening your filters or selecting a different date to find %s.', 'nexdirectory'), $plural_lower)); ?></p>
                </div>
            <?php endif; ?>
        </div>
        <div class="eep-pagination">
            <?php
            $pagination_html = (string) paginate_links([
                'current' => max(1, (int) $paged),
                'total' => max(1, (int) $query->max_num_pages),
                'add_args' => array_filter([
                    's' => sanitize_text_field((string) ($filters['s'] ?? '')),
                    'event_type' => sanitize_text_field((string) ($filters['event_type'] ?? '')),
                    'event_mode' => sanitize_text_field((string) ($filters['event_mode'] ?? '')),
                    'event_price' => sanitize_text_field((string) ($filters['event_price'] ?? '')),
                    'event_tag' => sanitize_text_field((string) ($filters['event_tag'] ?? '')),
                    'city' => sanitize_text_field((string) ($filters['city'] ?? '')),
                    'date_from' => sanitize_text_field((string) ($filters['date_from'] ?? '')),
                    'official' => sanitize_text_field((string) ($filters['official'] ?? '')),
                    'organizer' => $organizer_id > 0 ? (int) $organizer_id : '',
                ]),
            ]);
            if ($pagination_html !== '') {
                echo wp_kses_post($pagination_html);
            }
            ?>
        </div>
        <?php
        $html = ob_get_clean();
        wp_reset_postdata();
        return $html;
    }


    public function rest_get_archive_events($request) {
        $ui_labels = method_exists($this, 'get_site_profile_ui_labels') ? (array) $this->get_site_profile_ui_labels() : [];
        $profile_key = sanitize_key((string) ($ui_labels['profile_key'] ?? 'events'));
        $is_events_profile = $profile_key === 'events';
        $is_job_profile = $profile_key === 'job_board';
        $filters = [
            's' => sanitize_text_field((string) $request->get_param('s')),
            'event_type' => sanitize_text_field((string) $request->get_param('event_type')),
            'event_mode' => sanitize_text_field((string) $request->get_param('event_mode')),
            'event_price' => sanitize_text_field((string) $request->get_param('event_price')),
            'event_tag' => sanitize_text_field((string) $request->get_param('event_tag')),
            'city' => sanitize_text_field((string) $request->get_param('city')),
            'date_from' => sanitize_text_field((string) $request->get_param('date_from')),
            'official' => sanitize_text_field((string) $request->get_param('official')),
        ];
        $show_search_filter = (string) get_option('eep_map_filter_show_search', '1') === '1';
        $show_type_filter = (string) get_option('eep_map_filter_show_type', '1') === '1';
        $show_mode_filter = (string) get_option('eep_map_filter_show_mode', '1') === '1';
        $show_tag_filter = (string) get_option('eep_map_filter_show_tag', '0') === '1';
        $show_price_filter = (string) get_option('eep_map_filter_show_price', '1') === '1';
        $show_official_filter = (string) get_option('eep_map_filter_show_official', '1') === '1';
        $show_date_filter = (string) get_option('eep_map_filter_show_date', '1') === '1';
        if (!$show_search_filter) {
            $filters['s'] = '';
        }
        if (!$show_type_filter) {
            $filters['event_type'] = '';
        }
        if (!$show_mode_filter) {
            $filters['event_mode'] = '';
        }
        if (!$show_tag_filter) {
            $filters['event_tag'] = '';
        }
        if (!$show_price_filter) {
            $filters['event_price'] = '';
        }
        if (!$show_date_filter) {
            $filters['date_from'] = '';
        }
        if (!$show_official_filter) {
            $filters['official'] = '';
        }
        if ($is_job_profile) {
            $filters['event_price'] = '';
        }
        if (!$is_events_profile) {
            $filters['official'] = '';
        }
        $posts_per_page = max(1, min(60, (int) $request->get_param('posts_per_page')));
        $paged = max(1, (int) $request->get_param('paged'));
        $organizer_id = max(0, (int) $request->get_param('organizer'));
        if ($organizer_id <= 0) {
            $organizer_id = max(0, (int) $request->get_param('author'));
        }

        $query = $this->build_archive_query($filters, $posts_per_page, $paged, $organizer_id);
        $html = $this->render_archive_results_markup($query, $paged, $filters, $organizer_id);
        $archive_event_ids = [];
        foreach ((array) $query->posts as $post_item) {
            $candidate_id = is_object($post_item) ? (int) ($post_item->ID ?? 0) : (int) $post_item;
            if ($candidate_id > 0) {
                $archive_event_ids[] = $candidate_id;
            }
        }
        $archive_event_ids = array_values(array_unique($archive_event_ids));
        if (!empty($archive_event_ids)) {
            $this->record_analytics_for_event_ids($archive_event_ids, 'search_appearance', [
                'context' => 'archive',
            ]);
        }
        if (trim((string) ($filters['event_type'] ?? '')) !== '') {
            $this->record_analytics_event(0, 'search_filter', [
                'context' => 'archive',
                'meta_key' => 'event_type',
                'meta_value' => sanitize_key((string) ($filters['event_type'] ?? '')),
            ]);
        }
        if (trim((string) ($filters['event_mode'] ?? '')) !== '') {
            $this->record_analytics_event(0, 'search_filter', [
                'context' => 'archive',
                'meta_key' => 'event_mode',
                'meta_value' => sanitize_key((string) ($filters['event_mode'] ?? '')),
            ]);
        }
        if (trim((string) ($filters['event_tag'] ?? '')) !== '') {
            $this->record_analytics_event(0, 'search_filter', [
                'context' => 'archive',
                'meta_key' => 'event_tag',
                'meta_value' => sanitize_key((string) ($filters['event_tag'] ?? '')),
            ]);
        }
        if (trim((string) ($filters['event_price'] ?? '')) !== '') {
            $this->record_analytics_event(0, 'search_filter', [
                'context' => 'archive',
                'meta_key' => 'event_price',
                'meta_value' => sanitize_key((string) ($filters['event_price'] ?? '')),
            ]);
        }
        if (trim((string) ($filters['s'] ?? '')) !== '') {
            $this->record_analytics_event(0, 'search_query', [
                'context' => 'archive',
                'meta_key' => 'query',
                'meta_value' => sanitize_text_field((string) ($filters['s'] ?? '')),
            ]);
        }
        $has_active_filters = false;
        foreach ($filters as $value) {
            if (trim((string) $value) !== '') {
                $has_active_filters = true;
                break;
            }
        }
        if ($has_active_filters) {
            $post_ids_for_filter_options = $archive_event_ids;
            $filter_options = $this->build_archive_filter_options($post_ids_for_filter_options, $filters);
        } else {
            $filter_options = $this->build_global_archive_filter_options($filters);
        }

        return rest_ensure_response([
            'html' => $html,
            'max_pages' => (int) $query->max_num_pages,
            'count' => (int) $query->found_posts,
            'has_active_filters' => $has_active_filters,
            'filters' => $filter_options,
        ]);
    }


    public function rest_get_map_events($request) {
        $this->maybe_backfill_missing_event_coordinates(40);
        $map_ui_labels = method_exists($this, 'get_site_profile_ui_labels') ? (array) $this->get_site_profile_ui_labels() : [];
        $map_profile_key = sanitize_key((string) ($map_ui_labels['profile_key'] ?? 'events'));
        $is_events_profile = $map_profile_key === 'events';
        $is_job_profile = $map_profile_key === 'job_board';
        $north = is_numeric($request->get_param('north')) ? (float) $request->get_param('north') : 90.0;
        $south = is_numeric($request->get_param('south')) ? (float) $request->get_param('south') : -90.0;
        $east = is_numeric($request->get_param('east')) ? (float) $request->get_param('east') : 180.0;
        $west = is_numeric($request->get_param('west')) ? (float) $request->get_param('west') : -180.0;
        $north = max(-90, min(90, $north));
        $south = max(-90, min(90, $south));
        $east = max(-180, min(180, $east));
        $west = max(-180, min(180, $west));
        if ($south > $north) {
            $tmp = $south;
            $south = $north;
            $north = $tmp;
        }

        $max_events = max(12, min(500, (int) $request->get_param('max_events')));
        $page = max(1, (int) $request->get_param('page'));
        $per_page = max(1, min(250, (int) $request->get_param('per_page')));
        if ($per_page <= 1) {
            $per_page = min($max_events, max(20, (int) get_option('eep_map_results_per_page', 120)));
        }
        $search = sanitize_text_field((string) $request->get_param('s'));
        $type = sanitize_key((string) $request->get_param('event_type'));
        $mode = sanitize_key((string) $request->get_param('event_mode'));
        $tag = sanitize_key((string) $request->get_param('event_tag'));
        $price = sanitize_key((string) $request->get_param('event_price'));
        $date_from = sanitize_text_field((string) $request->get_param('date_from'));
        $official_param = (string) $request->get_param('official');
        if ($is_job_profile) {
            $price = '';
        }
        if (!$is_events_profile) {
            $official_param = '';
        }
        $distance_km = is_numeric($request->get_param('distance_km')) ? (float) $request->get_param('distance_km') : 0.0;
        $center_lat = is_numeric($request->get_param('center_lat')) ? (float) $request->get_param('center_lat') : null;
        $center_lng = is_numeric($request->get_param('center_lng')) ? (float) $request->get_param('center_lng') : null;
        $bucket_lat = round(($north + $south) / 2, 1);
        $bucket_lng = round(($east + $west) / 2, 1);
        $this->record_analytics_event(0, 'map_area_search', [
            'context' => 'map',
            'meta_key' => 'bbox',
            'meta_value' => (string) $bucket_lat . ',' . (string) $bucket_lng,
        ]);
        if ($type !== '') {
            $this->record_analytics_event(0, 'search_filter', [
                'context' => 'map',
                'meta_key' => 'event_type',
                'meta_value' => $type,
            ]);
        }
        if ($mode !== '') {
            $this->record_analytics_event(0, 'search_filter', [
                'context' => 'map',
                'meta_key' => 'event_mode',
                'meta_value' => $mode,
            ]);
        }
        if ($tag !== '') {
            $this->record_analytics_event(0, 'search_filter', [
                'context' => 'map',
                'meta_key' => 'event_tag',
                'meta_value' => $tag,
            ]);
        }
        if ($price !== '') {
            $this->record_analytics_event(0, 'search_filter', [
                'context' => 'map',
                'meta_key' => 'event_price',
                'meta_value' => $price,
            ]);
        }
        if ($search !== '') {
            $this->record_analytics_event(0, 'search_query', [
                'context' => 'map',
                'meta_key' => 'query',
                'meta_value' => $search,
            ]);
        }

        $tax_query = [];
        if ($type !== '') {
            $tax_query[] = [
                'taxonomy' => self::TAX_TYPE,
                'field' => 'slug',
                'terms' => $type,
            ];
        }
        if ($mode !== '') {
            $tax_query[] = [
                'taxonomy' => self::TAX_MODE,
                'field' => 'slug',
                'terms' => $mode,
            ];
        }
        if ($tag !== '') {
            $tax_query[] = [
                'taxonomy' => self::TAX_TAG,
                'field' => 'slug',
                'terms' => $tag,
            ];
        }
        if ($price !== '') {
            $tax_query[] = [
                'taxonomy' => self::TAX_PRICE,
                'field' => 'slug',
                'terms' => $price,
            ];
        }

        $meta_query = [
            'relation' => 'AND',
            [
                'key' => 'eep_lat',
                'value' => '',
                'compare' => '!=',
            ],
            [
                'key' => 'eep_lng',
                'value' => '',
                'compare' => '!=',
            ],
            [
                'key' => 'eep_lat',
                'value' => [$south, $north],
                'compare' => 'BETWEEN',
                'type' => 'DECIMAL',
            ],
        ];
        if ($west <= $east) {
            $meta_query[] = [
                'key' => 'eep_lng',
                'value' => [$west, $east],
                'compare' => 'BETWEEN',
                'type' => 'DECIMAL',
            ];
        } else {
            $meta_query[] = [
                'relation' => 'OR',
                [
                    'key' => 'eep_lng',
                    'value' => [$west, 180],
                    'compare' => 'BETWEEN',
                    'type' => 'DECIMAL',
                ],
                [
                    'key' => 'eep_lng',
                    'value' => [-180, $east],
                    'compare' => 'BETWEEN',
                    'type' => 'DECIMAL',
                ],
            ];
        }
        if ($date_from !== '') {
            $meta_query[] = [
                'key' => 'eep_start',
                'value' => $date_from,
                'compare' => '>=',
                'type' => 'CHAR',
            ];
        }
        if ($official_param !== '' && in_array($official_param, ['0', '1'], true)) {
            $meta_query[] = [
                'key' => 'eep_official_event',
                'value' => $official_param,
                'compare' => '=',
            ];
        }

        $post_statuses = ['publish'];
        if (is_user_logged_in() && current_user_can('manage_options')) {
            $post_statuses = ['publish', 'pending', 'draft', 'future'];
        }

        $args = [
            'post_type' => self::POST_TYPE,
            'post_status' => $post_statuses,
            'posts_per_page' => min($max_events, $per_page),
            'paged' => $page,
            'fields' => 'ids',
            's' => $search,
            'meta_key' => 'eep_start',
            'orderby' => 'meta_value',
            'order' => 'ASC',
            'eep_apply_boost_order' => 1,
            'no_found_rows' => false,
            'meta_query' => $meta_query,
            'update_post_meta_cache' => true,
            'update_post_term_cache' => true,
        ];
        if (!empty($tax_query)) {
            $args['tax_query'] = $tax_query;
        }

        $query = new WP_Query($args);
        $ids = is_array($query->posts) ? array_map('intval', $query->posts) : [];
        if (empty($ids)) {
            return rest_ensure_response([
                'events' => [],
                'pagination' => [
                    'page' => $page,
                    'per_page' => min($max_events, $per_page),
                    'max_pages' => 0,
                    'total' => 0,
                    'has_more' => false,
                ],
            ]);
        }
        update_meta_cache('post', $ids);
        update_object_term_cache($ids, self::POST_TYPE);
        $is_directory_profile = $map_profile_key === 'directory';
        $is_marketplace_profile = $map_profile_key === 'marketplace';

        $events = [];
        foreach ($ids as $event_id) {
            $lat_raw = get_post_meta($event_id, 'eep_lat', true);
            $lng_raw = get_post_meta($event_id, 'eep_lng', true);
            if (!is_numeric($lat_raw) || !is_numeric($lng_raw)) {
                continue;
            }
            $lat = (float) $lat_raw;
            $lng = (float) $lng_raw;
            if ($distance_km > 0 && $center_lat !== null && $center_lng !== null) {
                $distance = $this->haversine_distance_km($center_lat, $center_lng, $lat, $lng);
                if ($distance > $distance_km) {
                    continue;
                }
            }
            $cover = $this->get_event_cover_image_url($event_id, 'medium_large', 'thumbnail');
            $start_raw = (string) get_post_meta($event_id, 'eep_start', true);
            $start_ts = $start_raw !== '' ? strtotime($start_raw) : false;
            $map_date = $this->format_event_datetime_for_display($start_raw);
            if ($start_ts && $this->event_datetime_has_time($start_raw)) {
                $map_date = wp_date(get_option('date_format'), (int) $start_ts) . ' • ' . wp_date(get_option('time_format'), (int) $start_ts);
            }
            if ($map_date === '') {
                $map_date = $this->format_event_date($event_id);
            }
            $summary_line = '';
            if ($is_job_profile) {
                $closing_raw = trim((string) get_post_meta($event_id, 'eep_job_closing_date', true));
                $closing_ts = $closing_raw !== '' ? strtotime($closing_raw . ' 23:59:00') : false;
                if ($closing_ts) {
                    $map_date = sprintf(
                        /* translators: %s: date */
                        __('Closes %s', 'nexdirectory'),
                        wp_date(get_option('date_format'), (int) $closing_ts)
                    );
                }
                $summary_line = trim((string) get_post_meta($event_id, 'eep_job_salary', true));
                if ($summary_line === '') {
                    $summary_line = trim((string) get_post_meta($event_id, 'eep_company_name', true));
                }
            } elseif ($is_directory_profile) {
                $updated_ts = strtotime((string) get_post_field('post_modified', $event_id));
                if ($updated_ts) {
                    $map_date = sprintf(
                        /* translators: %s: date */
                        __('Updated %s', 'nexdirectory'),
                        wp_date(get_option('date_format'), (int) $updated_ts)
                    );
                }
                $type_terms = wp_get_post_terms($event_id, self::TAX_TYPE, ['fields' => 'names']);
                if (!is_wp_error($type_terms) && !empty($type_terms[0])) {
                    $summary_line = sanitize_text_field((string) $type_terms[0]);
                }
            } elseif ($is_marketplace_profile) {
                $listed_ts = strtotime((string) get_post_field('post_date', $event_id));
                if ($listed_ts) {
                    $map_date = sprintf(
                        /* translators: %s: date */
                        __('Listed %s', 'nexdirectory'),
                        wp_date(get_option('date_format'), (int) $listed_ts)
                    );
                }
                $summary_line = trim((string) get_post_meta($event_id, 'eep_market_condition', true));
                if ($summary_line === '') {
                    $summary_line = trim((string) get_post_meta($event_id, 'eep_price', true));
                }
            }
            $excerpt = get_the_excerpt($event_id);
            if (!is_string($excerpt) || trim($excerpt) === '') {
                $excerpt = wp_trim_words(wp_strip_all_tags((string) get_post_field('post_content', $event_id)), 20);
            }
            $location_line = implode(', ', array_filter([
                (string) get_post_meta($event_id, 'eep_venue', true),
                (string) get_post_meta($event_id, 'eep_address', true),
                (string) get_post_meta($event_id, 'eep_city', true),
                (string) get_post_meta($event_id, 'eep_country', true),
            ], static function ($value) {
                return trim((string) $value) !== '';
            }));
            $event_type_names = (array) wp_get_post_terms($event_id, self::TAX_TYPE, ['fields' => 'names']);
            if (is_wp_error($event_type_names)) {
                $event_type_names = [];
            }
            $event_type_slugs = (array) wp_get_post_terms($event_id, self::TAX_TYPE, ['fields' => 'slugs']);
            if (is_wp_error($event_type_slugs)) {
                $event_type_slugs = [];
            }
            $event_mode_names = (array) wp_get_post_terms($event_id, self::TAX_MODE, ['fields' => 'names']);
            if (is_wp_error($event_mode_names)) {
                $event_mode_names = [];
            }
            $event_mode_slugs = (array) wp_get_post_terms($event_id, self::TAX_MODE, ['fields' => 'slugs']);
            if (is_wp_error($event_mode_slugs)) {
                $event_mode_slugs = [];
            }
            $event_price_names = (array) wp_get_post_terms($event_id, self::TAX_PRICE, ['fields' => 'names']);
            if (is_wp_error($event_price_names)) {
                $event_price_names = [];
            }
            $event_price_slugs = (array) wp_get_post_terms($event_id, self::TAX_PRICE, ['fields' => 'slugs']);
            if (is_wp_error($event_price_slugs)) {
                $event_price_slugs = [];
            }
            if ($is_job_profile) {
                $job_type_label = trim((string) get_post_meta($event_id, 'eep_job_type', true));
                if ($job_type_label !== '') {
                    $event_type_names = [$job_type_label];
                }
                $job_work_mode = sanitize_key((string) get_post_meta($event_id, 'eep_job_work_mode', true));
                if ($job_work_mode === 'remote') {
                    $event_mode_names = [__('Remote', 'nexdirectory')];
                } elseif ($job_work_mode === 'hybrid') {
                    $event_mode_names = [__('Hybrid', 'nexdirectory')];
                } elseif ($job_work_mode === 'on-site') {
                    $event_mode_names = [__('On-site', 'nexdirectory')];
                }
                if ($summary_line !== '') {
                    $event_price_names = [$summary_line];
                } else {
                    $event_price_names = [];
                    $event_price_slugs = [];
                }
            } elseif ($is_marketplace_profile) {
                if ($summary_line !== '') {
                    $event_price_names = [$summary_line];
                }
            }
            $events[] = [
                'id' => $event_id,
                'title' => get_the_title($event_id),
                'url' => get_permalink($event_id),
                'city' => (string) get_post_meta($event_id, 'eep_city', true),
                'location' => trim((string) preg_replace('/\s+/u', ' ', $location_line)),
                'date' => $this->format_event_date($event_id),
                'date_short' => $map_date,
                'start' => (string) get_post_meta($event_id, 'eep_start', true),
                'date_key' => substr((string) get_post_meta($event_id, 'eep_start', true), 0, 10),
                'summary' => $summary_line,
                'excerpt' => trim((string) preg_replace('/\s+/u', ' ', (string) $excerpt)),
                'cover' => $cover,
                'type' => implode(', ', array_map('eep_sanitize_text', $event_type_names)),
                'type_slug' => implode(',', array_map('eep_sanitize_title', $event_type_slugs)),
                'mode' => implode(', ', array_map('eep_sanitize_text', $event_mode_names)),
                'mode_slug' => implode(',', array_map('eep_sanitize_title', $event_mode_slugs)),
                'tag' => implode(', ', wp_get_post_terms($event_id, self::TAX_TAG, ['fields' => 'names'])),
                'tag_slug' => implode(',', wp_get_post_terms($event_id, self::TAX_TAG, ['fields' => 'slugs'])),
                'price' => implode(', ', array_map('eep_sanitize_text', $event_price_names)),
                'price_slug' => implode(',', array_map('eep_sanitize_title', $event_price_slugs)),
                'official' => $this->get_official_event_flag($event_id) ? 1 : 0,
                'boosted' => method_exists($this, 'is_listing_boosted') && $this->is_listing_boosted($event_id) ? 1 : 0,
                'boost_badge' => sanitize_text_field((string) get_option('eep_boost_featured_badge_text', __('Featured', 'nexdirectory'))),
                'boost_highlight' => (string) get_option('eep_boost_highlight_style', '1') === '1' ? 1 : 0,
                'verified_badge' => (string) get_option('eep_boost_verified_badge_enabled', '0') === '1'
                    && get_user_meta((int) get_post_field('post_author', $event_id), 'eep_verified_organizer', true) === '1'
                    ? 1
                    : 0,
                'lat' => $lat,
                'lng' => $lng,
            ];
        }

        if (!empty($events)) {
            $event_ids = array_values(array_unique(array_filter(array_map(static function ($row) {
                return absint((int) ($row['id'] ?? 0));
            }, $events))));
            if (!empty($event_ids)) {
                $this->record_analytics_for_event_ids($event_ids, 'map_impression', [
                    'context' => 'map',
                ]);
                $this->record_analytics_for_event_ids($event_ids, 'search_appearance', [
                    'context' => 'map',
                ]);
            }
        }

        return rest_ensure_response([
            'events' => $events,
            'pagination' => [
                'page' => $page,
                'per_page' => min($max_events, $per_page),
                'max_pages' => (int) max(1, (int) $query->max_num_pages),
                'total' => (int) $query->found_posts,
                'has_more' => $page < (int) $query->max_num_pages,
            ],
        ]);
    }


    public function rest_geocode_address($request) {
        $address = sanitize_text_field((string) $request->get_param('address'));
        if ($address === '') {
            return new WP_Error('eep_address_missing', __('Address is required.', 'nexdirectory'), ['status' => 400]);
        }

        $ip = sanitize_text_field((string) ($_SERVER['REMOTE_ADDR'] ?? ''));
        $rate_key = 'eep_geo_' . md5($ip !== '' ? $ip : 'unknown');
        $rate_count = (int) get_transient($rate_key);
        if ($rate_count > 20) {
            return new WP_Error('eep_geocode_rate', __('Too many geocode attempts. Please wait and retry.', 'nexdirectory'), ['status' => 429]);
        }
        set_transient($rate_key, $rate_count + 1, HOUR_IN_SECONDS);

        $geo = $this->geocode_event_location_from_parts($address);
        if (!is_array($geo) || !isset($geo['lat'], $geo['lng']) || !is_numeric($geo['lat']) || !is_numeric($geo['lng'])) {
            return new WP_Error('eep_geocode_empty', __('No location found for that address.', 'nexdirectory'), ['status' => 404]);
        }
        return rest_ensure_response([
            'lat' => (float) $geo['lat'],
            'lng' => (float) $geo['lng'],
            'city' => sanitize_text_field((string) ($geo['city'] ?? '')),
            'country' => sanitize_text_field((string) ($geo['country'] ?? '')),
            'address' => sanitize_text_field((string) ($geo['address'] ?? $address)),
        ]);
    }


    public function rest_address_suggestions($request) {
        $query = sanitize_text_field((string) $request->get_param('q'));
        if ($query === '' || strlen($query) < 3) {
            return rest_ensure_response(['suggestions' => []]);
        }
        $limit = absint((int) $request->get_param('limit'));
        if ($limit < 1) {
            $limit = 6;
        }
        if ($limit > 10) {
            $limit = 10;
        }

        $ip = sanitize_text_field((string) ($_SERVER['REMOTE_ADDR'] ?? ''));
        $rate_key = 'eep_suggest_' . md5($ip !== '' ? $ip : 'unknown');
        $rate_count = (int) get_transient($rate_key);
        if ($rate_count > 60) {
            return new WP_Error('eep_suggest_rate', __('Too many lookups. Please pause and try again.', 'nexdirectory'), ['status' => 429]);
        }
        set_transient($rate_key, $rate_count + 1, HOUR_IN_SECONDS);

        $headers = [
            'Accept' => 'application/json',
            'User-Agent' => 'EventEventsPlatform/' . self::VERSION . ' (' . home_url('/') . ')',
        ];
        $suggestions = [];
        $google_key = (string) get_option('eep_google_maps_api_key', '');
        if ($google_key !== '') {
            $url = eep_add_query_arg([
                'input' => $query,
                'types' => 'geocode',
                'language' => get_locale(),
                'key' => $google_key,
            ], 'https://maps.googleapis.com/maps/api/place/autocomplete/json');
            $response = wp_remote_get($url, [
                'timeout' => 10,
                'headers' => $headers,
            ]);
            if (!is_wp_error($response)) {
                $payload = json_decode((string) wp_remote_retrieve_body($response), true);
                $predictions = is_array($payload) && !empty($payload['predictions']) && is_array($payload['predictions'])
                    ? (array) $payload['predictions']
                    : [];
                foreach (array_slice($predictions, 0, $limit) as $row) {
                    if (!is_array($row) || empty($row['description'])) {
                        continue;
                    }
                    $label = sanitize_text_field((string) $row['description']);
                    if ($label === '') {
                        continue;
                    }
                    $city = '';
                    $country = '';
                    if (!empty($row['terms']) && is_array($row['terms'])) {
                        $terms = array_values((array) $row['terms']);
                        if (count($terms) >= 2 && !empty($terms[count($terms) - 2]['value'])) {
                            $city = sanitize_text_field((string) $terms[count($terms) - 2]['value']);
                        }
                        if (count($terms) >= 1 && !empty($terms[count($terms) - 1]['value'])) {
                            $country = sanitize_text_field((string) $terms[count($terms) - 1]['value']);
                        }
                    }
                    $suggestions[] = [
                        'label' => $label,
                        'address' => $label,
                        'city' => $city,
                        'country' => $country,
                    ];
                }
            }
        }

        if (empty($suggestions)) {
            $url = eep_add_query_arg([
                'format' => 'json',
                'addressdetails' => 1,
                'limit' => $limit,
                'q' => $query,
            ], 'https://nominatim.openstreetmap.org/search');
            $response = wp_remote_get($url, [
                'timeout' => 10,
                'headers' => $headers,
            ]);
            if (is_wp_error($response)) {
                return new WP_Error('eep_suggest_failed', __('Could not load address suggestions.', 'nexdirectory'), ['status' => 500]);
            }
            $payload = json_decode((string) wp_remote_retrieve_body($response), true);
            if (!is_array($payload)) {
                $payload = [];
            }
            foreach (array_slice($payload, 0, $limit) as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $label = sanitize_text_field((string) ($row['display_name'] ?? ''));
                if ($label === '') {
                    continue;
                }
                $address_bits = !empty($row['address']) && is_array($row['address']) ? (array) $row['address'] : [];
                $city = '';
                foreach (['city', 'town', 'village', 'municipality', 'county', 'state'] as $city_key) {
                    if (!empty($address_bits[$city_key])) {
                        $city = sanitize_text_field((string) $address_bits[$city_key]);
                        break;
                    }
                }
                $country = !empty($address_bits['country']) ? sanitize_text_field((string) $address_bits['country']) : '';
                $suggestions[] = [
                    'label' => $label,
                    'address' => $label,
                    'city' => $city,
                    'country' => $country,
                    'lat' => isset($row['lat']) ? (float) $row['lat'] : null,
                    'lng' => isset($row['lon']) ? (float) $row['lon'] : null,
                ];
            }
        }

        $unique = [];
        foreach ($suggestions as $suggestion) {
            $label = sanitize_text_field((string) ($suggestion['label'] ?? ''));
            if ($label === '' || isset($unique[$label])) {
                continue;
            }
            $unique[$label] = [
                'label' => $label,
                'address' => sanitize_text_field((string) ($suggestion['address'] ?? $label)),
                'city' => sanitize_text_field((string) ($suggestion['city'] ?? '')),
                'country' => sanitize_text_field((string) ($suggestion['country'] ?? '')),
                'lat' => isset($suggestion['lat']) && is_numeric($suggestion['lat']) ? (float) $suggestion['lat'] : null,
                'lng' => isset($suggestion['lng']) && is_numeric($suggestion['lng']) ? (float) $suggestion['lng'] : null,
            ];
        }

        return rest_ensure_response([
            'suggestions' => array_values($unique),
        ]);
    }


    public function filter_frontend_media_library($query) {
        if (!wp_doing_ajax()) {
            return $query;
        }
        $action = isset($_REQUEST['action']) ? sanitize_key(wp_unslash($_REQUEST['action'])) : '';
        if ($action !== 'query-attachments') {
            return $query;
        }
        $referer = wp_get_referer();
        if ($referer && strpos($referer, admin_url()) === 0) {
            return $query;
        }
        if (!is_user_logged_in()) {
            $query['author'] = 0;
            return $query;
        }
        if ($this->user_can_moderate_events()) {
            return $query;
        }
        $query['author'] = get_current_user_id();
        return $query;
    }

}
