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

trait EEP_Frontend_Trait {
    public function render_events_archive($atts = []) {
        $atts = shortcode_atts([
            'posts_per_page' => 12,
            'show_sidebar' => 'yes',
        ], (array) $atts, 'eep_events_archive');
        $posts_per_page = max(1, (int) $atts['posts_per_page']);
        $show_sidebar = $atts['show_sidebar'] !== 'no';
        $ui_labels = $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';
        $show_price_filter = !$is_job_profile;
        $show_official_filter = $is_events_profile;
        $resolve_archive_visibility = static function ($option_key, $default) {
            return (string) get_option($option_key, $default ? '1' : '0') === '1';
        };
        $show_search_filter = $resolve_archive_visibility('eep_map_filter_show_search', true);
        $show_type_filter = $resolve_archive_visibility('eep_map_filter_show_type', true);
        $show_mode_filter = $resolve_archive_visibility('eep_map_filter_show_mode', false);
        $show_tag_filter = $resolve_archive_visibility('eep_map_filter_show_tag', false);
        $show_price_filter = $show_price_filter && $resolve_archive_visibility('eep_map_filter_show_price', true);
        $show_official_filter = $show_official_filter && $resolve_archive_visibility('eep_map_filter_show_official', false);
        $show_date_filter = $resolve_archive_visibility('eep_map_filter_show_date', true);

        $saved = is_user_logged_in() ? $this->get_saved_filter_values(get_current_user_id()) : [
            's' => '',
            'event_type' => '',
            'event_mode' => '',
            'event_price' => '',
            'event_tag' => '',
            'city' => '',
            'date_from' => '',
            'official' => '',
        ];
        $use_saved = isset($_GET['eep_use_saved']) && $_GET['eep_use_saved'] === '1';
        $organizer_id = isset($_GET['organizer']) ? absint($_GET['organizer']) : (isset($_GET['author']) ? absint($_GET['author']) : 0);
        $type = isset($_GET['event_type']) ? eep_sanitize_text(wp_unslash($_GET['event_type'])) : ($use_saved ? $saved['event_type'] : '');
        $mode = isset($_GET['event_mode']) ? eep_sanitize_text(wp_unslash($_GET['event_mode'])) : ($use_saved ? $saved['event_mode'] : '');
        $price_range = isset($_GET['event_price']) ? eep_sanitize_text(wp_unslash($_GET['event_price'])) : ($use_saved ? $saved['event_price'] : '');
        $tag = isset($_GET['event_tag']) ? eep_sanitize_text(wp_unslash($_GET['event_tag'])) : ($use_saved ? $saved['event_tag'] : '');
        $city = isset($_GET['city']) ? eep_sanitize_text(wp_unslash($_GET['city'])) : ($use_saved ? $saved['city'] : '');
        $date_from = isset($_GET['date_from']) ? eep_sanitize_text(wp_unslash($_GET['date_from'])) : ($use_saved ? $saved['date_from'] : '');
        $official = isset($_GET['official']) ? eep_sanitize_text(wp_unslash($_GET['official'])) : ($use_saved ? ($saved['official'] ?? '') : '');
        $search = isset($_GET['s']) ? eep_sanitize_text(wp_unslash($_GET['s'])) : ($use_saved ? $saved['s'] : '');
        $paged = max(1, get_query_var('paged'), isset($_GET['paged']) ? absint($_GET['paged']) : 1);
        $filters = [
            's' => $search,
            'event_type' => $type,
            'event_mode' => $mode,
            'event_price' => $price_range,
            'event_tag' => $tag,
            'city' => $city,
            'date_from' => $date_from,
            'official' => in_array($official, ['0', '1'], true) ? $official : '',
        ];
        $has_active_filters = false;
        foreach ($filters as $filter_value) {
            if (trim((string) $filter_value) !== '') {
                $has_active_filters = true;
                break;
            }
        }
        $query = $this->build_archive_query($filters, $posts_per_page, $paged, $organizer_id);

        if ($has_active_filters) {
            $post_ids_for_filter_options = [];
            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) {
                    $post_ids_for_filter_options[] = $candidate_id;
                }
            }
            $post_ids_for_filter_options = array_values(array_unique($post_ids_for_filter_options));
            $filter_options = $this->build_archive_filter_options($post_ids_for_filter_options, $filters);
        } else {
            $filter_options = $this->build_global_archive_filter_options($filters);
        }

        return $this->render_frontend_template('events-archive.php', [
            'show_sidebar' => $show_sidebar,
            'organizer_id' => $organizer_id,
            'posts_per_page' => $posts_per_page,
            'search' => $search,
            'type' => $type,
            'mode' => $mode,
            'price_range' => $price_range,
            'tag' => $tag,
            'city' => $city,
            'date_from' => $date_from,
            'paged' => $paged,
            'filters' => $filters,
            'has_active_filters' => $has_active_filters,
            'filter_options' => $filter_options,
            'query' => $query,
            'official' => in_array($official, ['0', '1'], true) ? $official : '',
            'show_search_filter' => $show_search_filter,
            'show_type_filter' => $show_type_filter,
            'show_mode_filter' => $show_mode_filter,
            'show_tag_filter' => $show_tag_filter,
            'show_date_filter' => $show_date_filter,
            'show_price_filter' => $show_price_filter,
            'show_official_filter' => $show_official_filter,
            'ui_labels' => $ui_labels,
        ]);
    }


    public function render_events_map($atts = []) {
        $atts = shortcode_atts([
            'max_events' => 200,
            'show_sidebar' => 'yes',
            'show_search' => '',
            'show_type' => '',
            'show_mode' => '',
            'show_tag' => '',
            'show_price' => '',
            'show_official' => '',
            'show_date' => '',
            'show_distance' => '',
            'show_list' => 'yes',
            'map_height' => 620,
            'view' => '',
            'layout' => '',
            'density' => '',
            'mobile_filters' => '',
        ], (array) $atts, 'eep_events_map');
        $max_events = max(12, (int) $atts['max_events']);
        $show_sidebar = $atts['show_sidebar'] !== 'no';
        $resolve_filter_visibility = static function ($shortcode_value, $option_key, $default) {
            $global_enabled = (string) get_option($option_key, $default ? '1' : '0') === '1';
            if (!$global_enabled) {
                return false;
            }
            if ($shortcode_value === '' || $shortcode_value === null) {
                return true;
            }
            $value = strtolower(trim((string) $shortcode_value));
            if (in_array($value, ['no', '0', 'false', 'off'], true)) {
                return false;
            }
            if (in_array($value, ['yes', '1', 'true', 'on'], true)) {
                return true;
            }
            return true;
        };
        $show_search = $resolve_filter_visibility($atts['show_search'], 'eep_map_filter_show_search', true);
        $show_type = $resolve_filter_visibility($atts['show_type'], 'eep_map_filter_show_type', true);
        $show_mode = $resolve_filter_visibility($atts['show_mode'], 'eep_map_filter_show_mode', false);
        $show_tag = $resolve_filter_visibility($atts['show_tag'], 'eep_map_filter_show_tag', false);
        $show_price = $resolve_filter_visibility($atts['show_price'], 'eep_map_filter_show_price', true);
        $show_official = $resolve_filter_visibility($atts['show_official'], 'eep_map_filter_show_official', false);
        $show_date = $resolve_filter_visibility($atts['show_date'], 'eep_map_filter_show_date', true);
        $show_distance = $resolve_filter_visibility($atts['show_distance'], 'eep_map_filter_show_distance', true);
        $show_list = $atts['show_list'] !== 'no';
        $map_height = max(320, min(1200, (int) $atts['map_height']));
        $default_view = sanitize_key((string) get_option('eep_map_view_template', 'default'));
        if (!in_array($default_view, ['default', 'triple'], true)) {
            $default_view = 'default';
        }
        $requested_view = sanitize_key((string) $atts['view']);
        $map_view = in_array($requested_view, ['default', 'triple'], true) ? $requested_view : $default_view;
        $layout = 'three-column';
        $default_density = sanitize_key((string) get_option('eep_map_results_density', 'comfortable'));
        if (!in_array($default_density, ['comfortable', 'compact'], true)) {
            $default_density = 'comfortable';
        }
        $density = $atts['density'] !== '' ? sanitize_key((string) $atts['density']) : $default_density;
        if (!in_array($density, ['comfortable', 'compact'], true)) {
            $density = $default_density;
        }
        $default_mobile_filters = sanitize_key((string) get_option('eep_map_mobile_filter_mode', 'drawer'));
        if (!in_array($default_mobile_filters, ['drawer', 'inline'], true)) {
            $default_mobile_filters = 'drawer';
        }
        $mobile_filters = $atts['mobile_filters'] !== '' ? sanitize_key((string) $atts['mobile_filters']) : $default_mobile_filters;
        if (!in_array($mobile_filters, ['drawer', 'inline'], true)) {
            $mobile_filters = $default_mobile_filters;
        }
        if (!$show_list && in_array($layout, ['three-column', 'below-list', 'below-grid', 'below-grid-large', 'grid-no-map', 'grid-no-map-large'], true)) {
            $layout = 'three-column';
        }
        $ui_labels = $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';
        if ($is_job_profile) {
            $show_price = false;
        }
        if (!$is_events_profile) {
            $show_official = false;
        }

        wp_enqueue_style('eep-leaflet');
        wp_enqueue_style('eep-leaflet-markercluster-default');
        wp_enqueue_script('eep-leaflet');
        wp_enqueue_script('eep-leaflet-markercluster');

        $template_file = $map_view === 'triple' ? 'MapPage-tripleColumn.php' : 'events-map.php';
        $template_path = plugin_dir_path(EEP_PLUGIN_FILE) . 'templates/frontend/' . $template_file;
        if (!is_readable($template_path)) {
            $template_file = 'events-map.php';
        }

        return $this->render_frontend_template($template_file, [
            'max_events' => $max_events,
            'show_sidebar' => $show_sidebar,
            'show_search' => $show_search,
            'show_type' => $show_type,
            'show_mode' => $show_mode,
            'show_tag' => $show_tag,
            'show_price' => $show_price,
            'show_official' => $show_official,
            'show_date' => $show_date,
            'show_distance' => $show_distance,
            'show_list' => $show_list,
            'map_height' => $map_height,
            'layout' => $layout,
            'density' => $density,
            'map_view' => $map_view,
            'mobile_filter_mode' => $mobile_filters,
            'ui_labels' => $ui_labels,
        ]);
    }


    public function render_event_webapp($atts = []) {
        static $event_webapp_rendered_ids = [];

        $atts = shortcode_atts([
            'event_id' => 0,
            'event' => 0,
        ], (array) $atts, 'eep_event_webapp');

        $event_id = absint((int) ($atts['event_id'] ?: $atts['event']));
        if ($event_id <= 0) {
            $event_id = absint((int) ($_GET['event_id'] ?? 0)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        }
        if ($event_id <= 0 && is_singular(self::POST_TYPE)) {
            $event_id = absint((int) get_the_ID());
        }

        if ($event_id <= 0 || get_post_type($event_id) !== self::POST_TYPE) {
            return '<div class="eep-notice error"><p>' . esc_html__('Select a valid event to open this web app.', 'nexdirectory') . '</p></div>';
        }
        if (!empty($event_webapp_rendered_ids[$event_id])) {
            return '';
        }
        $event_webapp_rendered_ids[$event_id] = true;

        $event_status = (string) get_post_status($event_id);
        $auth_identity = $this->resolve_event_webapp_auth_identity($event_id, null, true);
        $current_user_id = absint((int) ($auth_identity['user_id'] ?? 0));
        $can_manage = $this->event_webapp_user_can_manage_event($event_id, $current_user_id);
        if ($event_status !== 'publish' && !$can_manage) {
            return '<div class="eep-notice error"><p>' . esc_html__('This event app is not available yet.', 'nexdirectory') . '</p></div>';
        }

        $settings = $this->get_event_webapp_settings($event_id);
        if (empty($settings['enabled']) && !$can_manage) {
            return '<div class="eep-notice error"><p>' . esc_html__('The event organizer has not enabled the web app for this event.', 'nexdirectory') . '</p></div>';
        }
        if (!$this->event_webapp_user_can_access_event($event_id, $current_user_id, $settings, $can_manage)) {
            if ($current_user_id <= 0 && !empty($settings['requires_login'])) {
                $login_url = wp_login_url((string) $this->get_event_webapp_page_url($event_id));
                return '<div class="eep-notice error"><p>' . esc_html__('You must be logged in to open this event app.', 'nexdirectory') . '</p><p><a href="' . esc_url($login_url) . '">' . esc_html__('Log in now', 'nexdirectory') . '</a></p></div>';
            }
            return '<div class="eep-notice error"><p>' . esc_html__('You do not have access to this event app.', 'nexdirectory') . '</p></div>';
        }

        $bootstrap_payload = $this->build_event_webapp_bootstrap_payload($event_id, $current_user_id, $can_manage, $settings, $auth_identity);
        if (is_wp_error($bootstrap_payload)) {
            return '<div class="eep-notice error"><p>' . esc_html($bootstrap_payload->get_error_message()) . '</p></div>';
        }

        $event_webapp_page_url = $this->append_event_webapp_install_query_args($this->get_event_webapp_page_url($event_id), $auth_identity);
        $pwa_manifest_url = $this->append_event_webapp_install_query_args($this->get_event_webapp_manifest_url($event_id), $auth_identity);
        $pwa_service_worker_url = $this->append_event_webapp_install_query_args($this->get_event_webapp_service_worker_url($event_id), $auth_identity);

        return $this->render_frontend_template('event-webapp.php', [
            'event_id' => $event_id,
            'bootstrap_payload' => $bootstrap_payload,
            'bootstrap_url' => esc_url_raw(rest_url('eep/v1/event-webapp-bootstrap')),
            'chat_url' => esc_url_raw(rest_url('eep/v1/event-webapp-chat')),
            'dm_url' => esc_url_raw(rest_url('eep/v1/event-webapp-dm')),
            'qa_url' => esc_url_raw(rest_url('eep/v1/event-webapp-qa')),
            'notifications_url' => esc_url_raw(rest_url('eep/v1/event-webapp-notifications')),
            'itinerary_url' => esc_url_raw(rest_url('eep/v1/event-webapp-itinerary')),
            'rest_nonce' => wp_create_nonce('wp_rest'),
            'event_webapp_page_url' => $event_webapp_page_url,
            'pwa_manifest_url' => $pwa_manifest_url,
            'pwa_service_worker_url' => $pwa_service_worker_url,
            'pwa_service_worker_scope' => $this->get_event_webapp_service_worker_scope($event_id),
            'can_manage_event_webapp' => $can_manage,
            'install_identity' => $auth_identity,
        ]);
    }


    private function get_event_webapp_page_url($event_id = 0) {
        $event_id = absint((int) $event_id);
        $url = esc_url_raw((string) get_option('eep_page_event_webapp_url', ''));

        if ($url === '') {
            $page = get_page_by_path('event-app');
            if ($page instanceof WP_Post && !is_wp_error($page) && (string) get_post_status((int) $page->ID) === 'publish') {
                $url = esc_url_raw((string) get_permalink((int) $page->ID));
            }
        }
        if ($url === '') {
            $url = esc_url_raw((string) home_url('/event-app/'));
        }
        if ($event_id > 0) {
            $url = esc_url_raw((string) eep_add_query_arg('event_id', $event_id, $url));
        }

        return (string) $url;
    }


    private function get_event_webapp_manifest_url($event_id = 0) {
        $event_id = absint((int) $event_id);
        return esc_url_raw((string) eep_add_query_arg([
            'eep_webapp_manifest' => '1',
            'event_id' => $event_id,
        ], home_url('/')));
    }


    private function get_event_webapp_service_worker_url($event_id = 0) {
        $event_id = absint((int) $event_id);
        return esc_url_raw((string) eep_add_query_arg([
            'eep_webapp_sw' => '1',
            'event_id' => $event_id,
        ], home_url('/')));
    }


    private function get_event_webapp_service_worker_scope($event_id = 0) {
        $event_id = absint((int) $event_id);
        $page_url = (string) $this->get_event_webapp_page_url($event_id);
        $path = wp_parse_url($page_url, PHP_URL_PATH);
        $path = is_string($path) ? trim($path) : '';
        if ($path === '') {
            return '/';
        }
        return trailingslashit('/' . ltrim($path, '/'));
    }


    private function sanitize_widget_template($value, $fallback = '1') {
        $allowed = ['1', '2', '3', '4', '5', '6'];
        $value = sanitize_key((string) $value);
        if (in_array($value, $allowed, true)) {
            return $value;
        }
        return in_array((string) $fallback, $allowed, true) ? (string) $fallback : '1';
    }


    private function sanitize_widget_yes_no($value, $default = 'yes') {
        $value = strtolower(trim((string) $value));
        if (in_array($value, ['yes', '1', 'true', 'on'], true)) {
            return 'yes';
        }
        if (in_array($value, ['no', '0', 'false', 'off'], true)) {
            return 'no';
        }
        return strtolower(trim((string) $default)) === 'no' ? 'no' : 'yes';
    }


    private function parse_widget_slug_terms($raw) {
        $values = $this->parse_delimited_values($raw);
        $slugs = [];
        foreach ((array) $values as $value) {
            $slug = eep_sanitize_title((string) $value);
            if ($slug !== '') {
                $slugs[] = $slug;
            }
        }
        return array_values(array_unique($slugs));
    }


    private function sanitize_widget_custom_class($raw) {
        $items = preg_split('/\s+/', (string) $raw);
        if (!is_array($items)) {
            return '';
        }
        $classes = [];
        foreach ($items as $item) {
            $class_name = sanitize_html_class((string) $item);
            if ($class_name !== '') {
                $classes[] = $class_name;
            }
        }
        return implode(' ', array_values(array_unique($classes)));
    }


    private function sanitize_widget_custom_css($raw) {
        $css = trim((string) $raw);
        if ($css === '') {
            return '';
        }
        $css = str_ireplace(['<style', '</style'], '', $css);
        $css = str_replace(['<', '>'], '', $css);
        $css = preg_replace('/@import[^;]+;?/i', '', $css);
        return trim((string) $css);
    }


    private function build_widget_style_vars(array $style_config = []) {
        $style_bits = [];

        $accent = sanitize_hex_color((string) ($style_config['accent_color'] ?? ''));
        if ($accent) {
            $style_bits[] = '--eep-widget-accent:' . $accent;
        }
        $bg_color = sanitize_hex_color((string) ($style_config['bg_color'] ?? ''));
        if ($bg_color) {
            $style_bits[] = '--eep-widget-card-bg:' . $bg_color;
        }
        $text_color = sanitize_hex_color((string) ($style_config['text_color'] ?? ''));
        if ($text_color) {
            $style_bits[] = '--eep-widget-card-text:' . $text_color;
        }

        $radius = absint((int) ($style_config['radius'] ?? 0));
        if ($radius > 0) {
            $style_bits[] = '--eep-widget-card-radius:' . min(48, $radius) . 'px';
        }
        $gap = absint((int) ($style_config['gap'] ?? 0));
        if ($gap > 0) {
            $style_bits[] = '--eep-widget-gap:' . min(64, $gap) . 'px';
        }
        $image_height = absint((int) ($style_config['image_height'] ?? 0));
        if ($image_height > 0) {
            $style_bits[] = '--eep-widget-image-height:' . min(520, $image_height) . 'px';
        }
        $columns_desktop = absint((int) ($style_config['columns_desktop'] ?? 0));
        if ($columns_desktop > 0) {
            $style_bits[] = '--eep-widget-cols-desktop:' . max(1, min(6, $columns_desktop));
        }
        $columns_tablet = absint((int) ($style_config['columns_tablet'] ?? 0));
        if ($columns_tablet > 0) {
            $style_bits[] = '--eep-widget-cols-tablet:' . max(1, min(4, $columns_tablet));
        }
        $columns_mobile = absint((int) ($style_config['columns_mobile'] ?? 0));
        if ($columns_mobile > 0) {
            $style_bits[] = '--eep-widget-cols-mobile:' . max(1, min(2, $columns_mobile));
        }

        return empty($style_bits) ? '' : implode(';', $style_bits);
    }


    private function build_widget_query_config(array $atts = []) {
        $limit = max(1, min(60, absint((int) ($atts['limit'] ?? 6))));
        $search = sanitize_text_field((string) ($atts['search'] ?? ''));
        $city = sanitize_text_field((string) ($atts['city'] ?? ''));
        $date_from = sanitize_text_field((string) ($atts['date_from'] ?? ''));
        $official = sanitize_text_field((string) ($atts['official'] ?? ''));
        if (!in_array($official, ['0', '1'], true)) {
            $official = '';
        }

        $include_past = $this->sanitize_widget_yes_no($atts['include_past'] ?? 'no', 'no') === 'yes';
        if (!$include_past && $date_from === '') {
            $date_from = current_time('Y-m-d');
        }

        $orderby = sanitize_key((string) ($atts['orderby'] ?? 'start'));
        if (!in_array($orderby, ['start', 'date', 'title', 'modified', 'rand'], true)) {
            $orderby = 'start';
        }
        $order = strtoupper(sanitize_text_field((string) ($atts['order'] ?? 'ASC')));
        if (!in_array($order, ['ASC', 'DESC'], true)) {
            $order = 'ASC';
        }
        $tax_relation = strtoupper(sanitize_text_field((string) ($atts['tax_relation'] ?? 'AND')));
        if (!in_array($tax_relation, ['AND', 'OR'], true)) {
            $tax_relation = 'AND';
        }

        $custom_taxonomy = sanitize_key((string) ($atts['custom_taxonomy'] ?? ''));
        if ($custom_taxonomy !== '' && !taxonomy_exists($custom_taxonomy)) {
            $custom_taxonomy = '';
        }

        return [
            'limit' => $limit,
            'search' => $search,
            'city' => $city,
            'date_from' => $date_from,
            'official' => $official,
            'include_past' => $include_past,
            'orderby' => $orderby,
            'order' => $order,
            'tax_relation' => $tax_relation,
            'event_type_terms' => $this->parse_widget_slug_terms($atts['event_type'] ?? ''),
            'event_mode_terms' => $this->parse_widget_slug_terms($atts['event_mode'] ?? ''),
            'event_price_terms' => $this->parse_widget_slug_terms($atts['event_price'] ?? ''),
            'event_tag_terms' => $this->parse_widget_slug_terms($atts['event_tag'] ?? ''),
            'custom_taxonomy' => $custom_taxonomy,
            'custom_terms' => $this->parse_widget_slug_terms($atts['custom_terms'] ?? ''),
        ];
    }


    private function build_widget_events_query(array $query_config = []) {
        $tax_query = [];
        $add_tax_terms = static function (&$bucket, $taxonomy, $terms) {
            if (!taxonomy_exists((string) $taxonomy)) {
                return;
            }
            if (!is_array($terms) || empty($terms)) {
                return;
            }
            $bucket[] = [
                'taxonomy' => (string) $taxonomy,
                'field' => 'slug',
                'terms' => array_values(array_unique(array_filter(array_map('eep_sanitize_title', $terms)))),
            ];
        };

        $add_tax_terms($tax_query, self::TAX_TYPE, $query_config['event_type_terms'] ?? []);
        $add_tax_terms($tax_query, self::TAX_MODE, $query_config['event_mode_terms'] ?? []);
        $add_tax_terms($tax_query, self::TAX_PRICE, $query_config['event_price_terms'] ?? []);
        $add_tax_terms($tax_query, self::TAX_TAG, $query_config['event_tag_terms'] ?? []);

        $custom_taxonomy = sanitize_key((string) ($query_config['custom_taxonomy'] ?? ''));
        if ($custom_taxonomy !== '') {
            $add_tax_terms($tax_query, $custom_taxonomy, $query_config['custom_terms'] ?? []);
        }

        if (count($tax_query) > 1) {
            $tax_query = array_merge([
                'relation' => in_array((string) ($query_config['tax_relation'] ?? 'AND'), ['AND', 'OR'], true)
                    ? (string) $query_config['tax_relation']
                    : 'AND',
            ], $tax_query);
        }

        $meta_query = [];
        $city = sanitize_text_field((string) ($query_config['city'] ?? ''));
        if ($city !== '') {
            $meta_query[] = [
                'key' => 'eep_city',
                'value' => $city,
                'compare' => 'LIKE',
            ];
        }

        $date_from = sanitize_text_field((string) ($query_config['date_from'] ?? ''));
        if ($date_from !== '') {
            $meta_query[] = [
                'key' => 'eep_start',
                'value' => $date_from,
                'compare' => '>=',
                'type' => 'CHAR',
            ];
        }

        $official = sanitize_text_field((string) ($query_config['official'] ?? ''));
        if (in_array($official, ['0', '1'], true)) {
            $meta_query[] = [
                'key' => 'eep_official_event',
                'value' => $official,
                'compare' => '=',
            ];
        }

        $orderby = sanitize_key((string) ($query_config['orderby'] ?? 'start'));
        $order = in_array((string) ($query_config['order'] ?? 'ASC'), ['ASC', 'DESC'], true)
            ? (string) $query_config['order']
            : 'ASC';

        $query_args = [
            'post_type' => self::POST_TYPE,
            'post_status' => 'publish',
            'posts_per_page' => max(1, min(60, absint((int) ($query_config['limit'] ?? 6)))),
            'ignore_sticky_posts' => true,
            'no_found_rows' => false,
            'suppress_filters' => false,
            's' => sanitize_text_field((string) ($query_config['search'] ?? '')),
            'order' => $order,
        ];

        if (!empty($tax_query)) {
            $query_args['tax_query'] = $tax_query;
        }
        if (!empty($meta_query)) {
            $query_args['meta_query'] = $meta_query;
        }

        switch ($orderby) {
            case 'date':
                $query_args['orderby'] = 'date';
                break;
            case 'title':
                $query_args['orderby'] = 'title';
                break;
            case 'modified':
                $query_args['orderby'] = 'modified';
                break;
            case 'rand':
                $query_args['orderby'] = 'rand';
                unset($query_args['order']);
                break;
            case 'start':
            default:
                $query_args['meta_key'] = 'eep_start';
                $query_args['orderby'] = 'meta_value';
                break;
        }

        return new WP_Query($query_args);
    }


    private function get_widget_event_location_label($post_id) {
        $city = sanitize_text_field((string) get_post_meta($post_id, 'eep_city', true));
        $country = sanitize_text_field((string) get_post_meta($post_id, 'eep_country', true));
        $venue = sanitize_text_field((string) get_post_meta($post_id, 'eep_venue', true));
        $address = sanitize_text_field((string) get_post_meta($post_id, 'eep_address', true));

        $parts = [];
        if ($city !== '') {
            $parts[] = $city;
        }
        if ($country !== '') {
            $parts[] = $country;
        }
        if (empty($parts) && $venue !== '') {
            $parts[] = $venue;
        }
        if (empty($parts) && $address !== '') {
            $parts[] = $address;
        }
        return implode(', ', $parts);
    }


    private function get_widget_event_badges($post_id) {
        $badges = [];
        $mode_terms = wp_get_post_terms($post_id, self::TAX_MODE, ['fields' => 'names']);
        $price_terms = wp_get_post_terms($post_id, self::TAX_PRICE, ['fields' => 'names']);
        $type_terms = wp_get_post_terms($post_id, self::TAX_TYPE, ['fields' => 'names']);
        $official_value = (string) get_post_meta($post_id, 'eep_official_event', true);
        if ($official_value === '1') {
            $classification_labels = method_exists($this, 'get_classification_labels') ? (array) $this->get_classification_labels() : [];
            $badges[] = sanitize_text_field((string) ($classification_labels['official'] ?? __('Official', 'nexdirectory')));
        }
        if (!empty($mode_terms) && isset($mode_terms[0])) {
            $badges[] = sanitize_text_field((string) $mode_terms[0]);
        }
        if (!empty($price_terms) && isset($price_terms[0])) {
            $badges[] = sanitize_text_field((string) $price_terms[0]);
        }
        if (!empty($type_terms) && isset($type_terms[0])) {
            $badges[] = sanitize_text_field((string) $type_terms[0]);
        }
        return array_slice(array_values(array_unique(array_filter($badges))), 0, 4);
    }


    private function get_widget_bento_size_class($template, $index) {
        $template = $this->sanitize_widget_template($template, '1');
        $patterns = [
            '1' => ['eep-bento-item--wide', 'eep-bento-item--tall', '', '', 'eep-bento-item--wide', ''],
            '2' => ['eep-bento-item--large', '', 'eep-bento-item--wide', '', 'eep-bento-item--tall', ''],
            '3' => ['eep-bento-item--tall', 'eep-bento-item--wide', '', 'eep-bento-item--large', '', ''],
            '4' => ['eep-bento-item--large', '', '', 'eep-bento-item--wide', '', 'eep-bento-item--tall'],
            '5' => ['eep-bento-item--wide', '', 'eep-bento-item--tall', '', 'eep-bento-item--large', ''],
            '6' => ['eep-bento-item--tall', '', 'eep-bento-item--wide', '', '', 'eep-bento-item--large'],
        ];
        $pattern = isset($patterns[$template]) ? (array) $patterns[$template] : (array) $patterns['1'];
        if (empty($pattern)) {
            return '';
        }
        return (string) $pattern[$index % count($pattern)];
    }


    private function render_widget_event_item($post_id, array $options = []) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return '';
        }
        $view = sanitize_key((string) ($options['view'] ?? 'grid'));
        if (!in_array($view, ['grid', 'bento', 'search'], true)) {
            $view = 'grid';
        }

        $title = get_the_title($post_id);
        $url = get_permalink($post_id);
        $cover = $this->get_event_cover_image_url($post_id, 'medium_large', 'thumbnail');
        $date_label = $this->format_event_date($post_id);
        $location_label = $this->get_widget_event_location_label($post_id);
        $badges = $this->get_widget_event_badges($post_id);
        $template = $this->sanitize_widget_template($options['template'] ?? '1', '1');
        $show_meta = $this->sanitize_widget_yes_no($options['show_meta'] ?? 'yes', 'yes') === 'yes';
        $show_excerpt = $this->sanitize_widget_yes_no($options['show_excerpt'] ?? 'yes', 'yes') === 'yes';
        $excerpt_length = max(8, min(70, absint((int) ($options['excerpt_length'] ?? 20))));
        $excerpt = eep_strip_all_tags((string) get_the_excerpt($post_id));
        if ($excerpt !== '') {
            $excerpt = wp_trim_words($excerpt, $excerpt_length, '…');
        }

        if ($view === 'search') {
            ob_start();
            ?>
            <article class="eep-widget-search-item eep-widget-search-item--template-<?php echo esc_attr($template); ?>" data-eep-event-id="<?php echo esc_attr((string) $post_id); ?>">
                <a class="eep-widget-search-link" href="<?php echo esc_url((string) $url); ?>">
                    <span class="eep-widget-search-thumb-wrap">
                        <?php if ($cover !== '') : ?>
                            <img class="eep-widget-search-thumb" src="<?php echo esc_url((string) $cover); ?>" alt="<?php echo esc_attr((string) $title); ?>" loading="lazy" decoding="async" fetchpriority="low">
                        <?php else : ?>
                            <span class="eep-widget-search-thumb eep-widget-search-thumb--fallback" aria-hidden="true"></span>
                        <?php endif; ?>
                    </span>
                    <span class="eep-widget-search-body">
                        <strong class="eep-widget-search-title"><?php echo esc_html((string) $title); ?></strong>
                        <?php if ($show_meta && $date_label !== '') : ?>
                            <span class="eep-widget-search-meta"><?php echo esc_html((string) $date_label); ?></span>
                        <?php endif; ?>
                        <?php if ($show_meta && $location_label !== '') : ?>
                            <span class="eep-widget-search-meta"><?php echo esc_html((string) $location_label); ?></span>
                        <?php endif; ?>
                        <?php if (!empty($badges)) : ?>
                            <span class="eep-widget-search-chips">
                                <?php foreach ($badges as $badge) : ?>
                                    <span class="eep-widget-chip"><?php echo esc_html((string) $badge); ?></span>
                                <?php endforeach; ?>
                            </span>
                        <?php endif; ?>
                    </span>
                </a>
            </article>
            <?php
            return (string) ob_get_clean();
        }

        $card_classes = [
            'eep-widget-card',
            'eep-widget-card--' . $view,
            'eep-widget-card--template-' . $template,
        ];
        if ($view === 'bento') {
            $card_classes[] = $this->get_widget_bento_size_class($template, absint((int) ($options['index'] ?? 0)));
            $hover_effect = sanitize_key((string) ($options['hover_effect'] ?? 'lift'));
            if (!in_array($hover_effect, ['lift', 'zoom', 'glow', 'tilt'], true)) {
                $hover_effect = 'lift';
            }
            $card_classes[] = 'eep-widget-hover--' . $hover_effect;
        }
        $card_classes = implode(' ', array_filter(array_map('sanitize_html_class', $card_classes)));

        ob_start();
        ?>
        <article class="<?php echo esc_attr($card_classes); ?>" data-eep-event-id="<?php echo esc_attr((string) $post_id); ?>">
            <a class="eep-widget-card-link" href="<?php echo esc_url((string) $url); ?>">
                <span class="eep-widget-card-media">
                    <?php if ($cover !== '') : ?>
                        <img class="eep-widget-card-image" src="<?php echo esc_url((string) $cover); ?>" alt="<?php echo esc_attr((string) $title); ?>" loading="lazy" decoding="async" fetchpriority="low">
                    <?php else : ?>
                        <span class="eep-widget-card-image eep-widget-card-image--fallback" aria-hidden="true"></span>
                    <?php endif; ?>
                </span>
                <span class="eep-widget-card-body">
                    <strong class="eep-widget-card-title"><?php echo esc_html((string) $title); ?></strong>
                    <?php if ($show_meta && $date_label !== '') : ?>
                        <span class="eep-widget-card-meta"><?php echo esc_html((string) $date_label); ?></span>
                    <?php endif; ?>
                    <?php if ($show_meta && $location_label !== '') : ?>
                        <span class="eep-widget-card-meta"><?php echo esc_html((string) $location_label); ?></span>
                    <?php endif; ?>
                    <?php if (!empty($badges)) : ?>
                        <span class="eep-widget-card-chips">
                            <?php foreach ($badges as $badge) : ?>
                                <span class="eep-widget-chip"><?php echo esc_html((string) $badge); ?></span>
                            <?php endforeach; ?>
                        </span>
                    <?php endif; ?>
                    <?php if ($show_excerpt && $excerpt !== '') : ?>
                        <span class="eep-widget-card-excerpt"><?php echo esc_html((string) $excerpt); ?></span>
                    <?php endif; ?>
                    <span class="eep-widget-card-cta"><?php esc_html_e('View Details', 'nexdirectory'); ?></span>
                </span>
            </a>
        </article>
        <?php
        return (string) ob_get_clean();
    }


    private function render_widget_events_markup($query, array $options = []) {
        $view = sanitize_key((string) ($options['view'] ?? 'grid'));
        if (!in_array($view, ['grid', 'bento', 'search'], true)) {
            $view = 'grid';
        }
        $template = $this->sanitize_widget_template($options['template'] ?? '1', '1');
        $wrapper_classes = [
            'eep-widget-results',
            'eep-widget-results--' . $view,
            'eep-widget-results--template-' . $template,
        ];
        if ($view === 'bento') {
            $wrapper_classes[] = 'eep-widget-bento-grid';
        } elseif ($view === 'search') {
            $wrapper_classes[] = 'eep-widget-search-results';
        } else {
            $wrapper_classes[] = 'eep-widget-grid-results';
        }

        $empty_text = sanitize_text_field((string) ($options['empty_text'] ?? __('No matching listings found.', 'nexdirectory')));
        ob_start();
        ?>
        <div class="<?php echo esc_attr(implode(' ', array_map('sanitize_html_class', $wrapper_classes))); ?>">
            <?php if ($query instanceof WP_Query && $query->have_posts()) : ?>
                <?php
                $index = 0;
                while ($query->have_posts()) :
                    $query->the_post();
                    echo $this->render_widget_event_item(get_the_ID(), array_merge($options, ['index' => $index])); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                    $index++;
                endwhile;
                wp_reset_postdata();
                ?>
            <?php else : ?>
                <div class="eep-widget-empty"><?php echo esc_html((string) $empty_text); ?></div>
            <?php endif; ?>
        </div>
        <?php
        return (string) ob_get_clean();
    }


    public function render_events_query_grid($atts = []) {
        $atts = shortcode_atts([
            'title' => '',
            'subtitle' => '',
            'template' => '1',
            'limit' => 9,
            'orderby' => 'start',
            'order' => 'ASC',
            'search' => '',
            'event_type' => '',
            'event_mode' => '',
            'event_tag' => '',
            'event_price' => '',
            'city' => '',
            'date_from' => '',
            'official' => '',
            'custom_taxonomy' => '',
            'custom_terms' => '',
            'tax_relation' => 'AND',
            'include_past' => 'no',
            'show_excerpt' => 'yes',
            'show_meta' => 'yes',
            'excerpt_length' => 20,
            'columns_desktop' => 3,
            'columns_tablet' => 2,
            'columns_mobile' => 1,
            'gap' => 24,
            'radius' => 18,
            'image_height' => 220,
            'accent_color' => '',
            'bg_color' => '',
            'text_color' => '',
            'custom_class' => '',
            'custom_css' => '',
        ], (array) $atts, 'eep_events_query_grid');

        $query_config = $this->build_widget_query_config($atts);
        $query = $this->build_widget_events_query($query_config);
        $wrapper_id = sanitize_html_class(wp_unique_id('eep-widget-grid-'));
        $template = $this->sanitize_widget_template($atts['template'], '1');
        $style_vars = $this->build_widget_style_vars([
            'accent_color' => $atts['accent_color'],
            'bg_color' => $atts['bg_color'],
            'text_color' => $atts['text_color'],
            'radius' => $atts['radius'],
            'gap' => $atts['gap'],
            'image_height' => $atts['image_height'],
            'columns_desktop' => $atts['columns_desktop'],
            'columns_tablet' => $atts['columns_tablet'],
            'columns_mobile' => $atts['columns_mobile'],
        ]);
        $custom_class = $this->sanitize_widget_custom_class($atts['custom_class']);
        $custom_css = $this->sanitize_widget_custom_css($atts['custom_css']);
        $results_markup = $this->render_widget_events_markup($query, [
            'view' => 'grid',
            'template' => $template,
            'show_excerpt' => $atts['show_excerpt'],
            'show_meta' => $atts['show_meta'],
            'excerpt_length' => $atts['excerpt_length'],
            'empty_text' => __('No matching listings found for this grid query.', 'nexdirectory'),
        ]);

        return $this->render_frontend_template('widget-events-grid.php', [
            'wrapper_id' => $wrapper_id,
            'template' => $template,
            'title' => sanitize_text_field((string) $atts['title']),
            'subtitle' => sanitize_text_field((string) $atts['subtitle']),
            'results_markup' => $results_markup,
            'style_vars' => $style_vars,
            'custom_class' => $custom_class,
            'custom_css' => $custom_css,
            'query_count' => $query instanceof WP_Query ? (int) $query->post_count : 0,
        ]);
    }


    public function render_events_bento_grid($atts = []) {
        $atts = shortcode_atts([
            'title' => '',
            'subtitle' => '',
            'template' => '1',
            'limit' => 8,
            'orderby' => 'start',
            'order' => 'ASC',
            'search' => '',
            'event_type' => '',
            'event_mode' => '',
            'event_tag' => '',
            'event_price' => '',
            'city' => '',
            'date_from' => '',
            'official' => '',
            'custom_taxonomy' => '',
            'custom_terms' => '',
            'tax_relation' => 'AND',
            'include_past' => 'no',
            'show_excerpt' => 'yes',
            'show_meta' => 'yes',
            'excerpt_length' => 18,
            'hover_effect' => 'lift',
            'columns_desktop' => 4,
            'columns_tablet' => 2,
            'columns_mobile' => 1,
            'gap' => 22,
            'radius' => 18,
            'image_height' => 220,
            'accent_color' => '',
            'bg_color' => '',
            'text_color' => '',
            'custom_class' => '',
            'custom_css' => '',
        ], (array) $atts, 'eep_events_bento_grid');

        $query_config = $this->build_widget_query_config($atts);
        $query = $this->build_widget_events_query($query_config);
        $wrapper_id = sanitize_html_class(wp_unique_id('eep-widget-bento-'));
        $template = $this->sanitize_widget_template($atts['template'], '1');
        $style_vars = $this->build_widget_style_vars([
            'accent_color' => $atts['accent_color'],
            'bg_color' => $atts['bg_color'],
            'text_color' => $atts['text_color'],
            'radius' => $atts['radius'],
            'gap' => $atts['gap'],
            'image_height' => $atts['image_height'],
            'columns_desktop' => $atts['columns_desktop'],
            'columns_tablet' => $atts['columns_tablet'],
            'columns_mobile' => $atts['columns_mobile'],
        ]);
        $custom_class = $this->sanitize_widget_custom_class($atts['custom_class']);
        $custom_css = $this->sanitize_widget_custom_css($atts['custom_css']);
        $results_markup = $this->render_widget_events_markup($query, [
            'view' => 'bento',
            'template' => $template,
            'show_excerpt' => $atts['show_excerpt'],
            'show_meta' => $atts['show_meta'],
            'excerpt_length' => $atts['excerpt_length'],
            'hover_effect' => $atts['hover_effect'],
            'empty_text' => __('No matching listings found for this bento query.', 'nexdirectory'),
        ]);

        return $this->render_frontend_template('widget-events-bento.php', [
            'wrapper_id' => $wrapper_id,
            'template' => $template,
            'title' => sanitize_text_field((string) $atts['title']),
            'subtitle' => sanitize_text_field((string) $atts['subtitle']),
            'results_markup' => $results_markup,
            'style_vars' => $style_vars,
            'custom_class' => $custom_class,
            'custom_css' => $custom_css,
            'query_count' => $query instanceof WP_Query ? (int) $query->post_count : 0,
        ]);
    }


    public function render_events_ajax_search($atts = []) {
        $atts = shortcode_atts([
            'title' => '',
            'subtitle' => '',
            'template' => '1',
            'limit' => 8,
            'min_chars' => 2,
            'search_placeholder' => __('Search listings by keyword...', 'nexdirectory'),
            'orderby' => 'start',
            'order' => 'ASC',
            'search' => '',
            'event_type' => '',
            'event_mode' => '',
            'event_tag' => '',
            'event_price' => '',
            'city' => '',
            'date_from' => '',
            'official' => '',
            'custom_taxonomy' => '',
            'custom_terms' => '',
            'tax_relation' => 'AND',
            'include_past' => 'no',
            'show_type' => 'yes',
            'show_mode' => 'yes',
            'show_tag' => 'yes',
            'show_price' => 'yes',
            'show_city' => 'yes',
            'show_date' => 'yes',
            'show_official' => 'no',
            'show_excerpt' => 'no',
            'show_meta' => 'yes',
            'excerpt_length' => 14,
            'gap' => 14,
            'radius' => 14,
            'accent_color' => '',
            'bg_color' => '',
            'text_color' => '',
            'custom_class' => '',
            'custom_css' => '',
        ], (array) $atts, 'eep_events_ajax_search');

        $query_config = $this->build_widget_query_config($atts);
        $query = $this->build_widget_events_query($query_config);
        $wrapper_id = sanitize_html_class(wp_unique_id('eep-widget-search-'));
        $template = $this->sanitize_widget_template($atts['template'], '1');
        $style_vars = $this->build_widget_style_vars([
            'accent_color' => $atts['accent_color'],
            'bg_color' => $atts['bg_color'],
            'text_color' => $atts['text_color'],
            'radius' => $atts['radius'],
            'gap' => $atts['gap'],
        ]);
        $custom_class = $this->sanitize_widget_custom_class($atts['custom_class']);
        $custom_css = $this->sanitize_widget_custom_css($atts['custom_css']);

        $show_type = $this->sanitize_widget_yes_no($atts['show_type'], 'yes') === 'yes';
        $show_mode = $this->sanitize_widget_yes_no($atts['show_mode'], 'yes') === 'yes';
        $show_tag = $this->sanitize_widget_yes_no($atts['show_tag'], 'yes') === 'yes';
        $show_price = $this->sanitize_widget_yes_no($atts['show_price'], 'yes') === 'yes';
        $show_city = $this->sanitize_widget_yes_no($atts['show_city'], 'yes') === 'yes';
        $show_date = $this->sanitize_widget_yes_no($atts['show_date'], 'yes') === 'yes';
        $show_official = $this->sanitize_widget_yes_no($atts['show_official'], 'no') === 'yes';
        $results_markup = $this->render_widget_events_markup($query, [
            'view' => 'search',
            'template' => $template,
            'show_excerpt' => $atts['show_excerpt'],
            'show_meta' => $atts['show_meta'],
            'excerpt_length' => $atts['excerpt_length'],
            'empty_text' => __('No listings match your current filters.', 'nexdirectory'),
        ]);

        $client_config = [
            'endpoint' => esc_url_raw(rest_url('eep/v1/widget-search')),
            'template' => $template,
            'view' => 'search',
            'limit' => max(1, min(60, absint((int) $atts['limit']))),
            'minChars' => max(0, min(8, absint((int) $atts['min_chars']))),
            'showExcerpt' => $this->sanitize_widget_yes_no($atts['show_excerpt'], 'no'),
            'showMeta' => $this->sanitize_widget_yes_no($atts['show_meta'], 'yes'),
            'excerptLength' => max(8, min(70, absint((int) $atts['excerpt_length']))),
            'order' => in_array(strtoupper((string) $atts['order']), ['ASC', 'DESC'], true) ? strtoupper((string) $atts['order']) : 'ASC',
            'orderby' => in_array((string) $atts['orderby'], ['start', 'date', 'title', 'modified', 'rand'], true) ? (string) $atts['orderby'] : 'start',
            'taxRelation' => in_array(strtoupper((string) $atts['tax_relation']), ['AND', 'OR'], true) ? strtoupper((string) $atts['tax_relation']) : 'AND',
            'customTaxonomy' => sanitize_key((string) $atts['custom_taxonomy']),
            'customTerms' => implode(',', $this->parse_widget_slug_terms($atts['custom_terms'])),
            'includePast' => $this->sanitize_widget_yes_no($atts['include_past'], 'no'),
        ];

        return $this->render_frontend_template('widget-events-ajax-search.php', [
            'wrapper_id' => $wrapper_id,
            'template' => $template,
            'title' => sanitize_text_field((string) $atts['title']),
            'subtitle' => sanitize_text_field((string) $atts['subtitle']),
            'search_placeholder' => sanitize_text_field((string) $atts['search_placeholder']),
            'style_vars' => $style_vars,
            'custom_class' => $custom_class,
            'custom_css' => $custom_css,
            'results_markup' => $results_markup,
            'result_count' => $query instanceof WP_Query ? (int) $query->post_count : 0,
            'show_type' => $show_type,
            'show_mode' => $show_mode,
            'show_tag' => $show_tag,
            'show_price' => $show_price,
            'show_city' => $show_city,
            'show_date' => $show_date,
            'show_official' => $show_official,
            'event_type_options' => $show_type ? get_terms(['taxonomy' => self::TAX_TYPE, 'hide_empty' => true]) : [],
            'event_mode_options' => $show_mode ? get_terms(['taxonomy' => self::TAX_MODE, 'hide_empty' => true]) : [],
            'event_tag_options' => $show_tag ? get_terms(['taxonomy' => self::TAX_TAG, 'hide_empty' => true]) : [],
            'event_price_options' => $show_price ? get_terms(['taxonomy' => self::TAX_PRICE, 'hide_empty' => true]) : [],
            'search_config' => $client_config,
            'default_filters' => [
                'search' => sanitize_text_field((string) $atts['search']),
                'event_type' => sanitize_text_field((string) $atts['event_type']),
                'event_mode' => sanitize_text_field((string) $atts['event_mode']),
                'event_tag' => sanitize_text_field((string) $atts['event_tag']),
                'event_price' => sanitize_text_field((string) $atts['event_price']),
                'city' => sanitize_text_field((string) $atts['city']),
                'date_from' => sanitize_text_field((string) $atts['date_from']),
                'official' => sanitize_text_field((string) $atts['official']),
            ],
        ]);
    }


    public function rest_widget_search($request) {
        $params = [
            'template' => sanitize_text_field((string) $request->get_param('template')),
            'limit' => (int) $request->get_param('limit'),
            'orderby' => sanitize_text_field((string) $request->get_param('orderby')),
            'order' => sanitize_text_field((string) $request->get_param('order')),
            'search' => sanitize_text_field((string) $request->get_param('search')),
            'event_type' => sanitize_text_field((string) $request->get_param('event_type')),
            'event_mode' => sanitize_text_field((string) $request->get_param('event_mode')),
            'event_tag' => sanitize_text_field((string) $request->get_param('event_tag')),
            'event_price' => sanitize_text_field((string) $request->get_param('event_price')),
            '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')),
            'custom_taxonomy' => sanitize_key((string) $request->get_param('custom_taxonomy')),
            'custom_terms' => sanitize_text_field((string) $request->get_param('custom_terms')),
            'tax_relation' => sanitize_text_field((string) $request->get_param('tax_relation')),
            'include_past' => sanitize_text_field((string) $request->get_param('include_past')),
            'show_excerpt' => sanitize_text_field((string) $request->get_param('show_excerpt')),
            'show_meta' => sanitize_text_field((string) $request->get_param('show_meta')),
            'excerpt_length' => (int) $request->get_param('excerpt_length'),
        ];
        $view = sanitize_key((string) $request->get_param('view'));
        if (!in_array($view, ['grid', 'bento', 'search'], true)) {
            $view = 'search';
        }
        $query_config = $this->build_widget_query_config($params);
        $query = $this->build_widget_events_query($query_config);
        $template = $this->sanitize_widget_template($params['template'] ?? '1', '1');

        $html = $this->render_widget_events_markup($query, [
            'view' => $view,
            'template' => $template,
            'show_excerpt' => $params['show_excerpt'] ?? 'no',
            'show_meta' => $params['show_meta'] ?? 'yes',
            'excerpt_length' => $params['excerpt_length'] ?? 14,
            'empty_text' => __('No listings match your current filters.', 'nexdirectory'),
        ]);

        return rest_ensure_response([
            'html' => $html,
            'count' => $query instanceof WP_Query ? (int) $query->post_count : 0,
            'found' => $query instanceof WP_Query ? (int) $query->found_posts : 0,
        ]);
    }


    public function render_event_chatbot($atts = []) {
        if (function_exists('epw_fs') && !epw_fs()->can_use_premium_code__premium_only()) {
            if (current_user_can('manage_options')) {
                return '<div class="eep-chatbot-disabled-notice">' . esc_html__('AI Chatbot is a premium feature. Activate a valid license to enable it.', 'nexdirectory') . '</div>';
            }
            return '';
        }

        $atts = shortcode_atts([
            'position' => '',
            'position_desktop' => '',
            'position_mobile' => '',
            'name' => '',
            'welcome' => '',
            'theme_color' => '',
            'text_color' => '',
        ], (array) $atts, 'eep_event_chatbot');

        $enabled = (string) get_option('eep_chatbot_enabled', '0') === '1';
        if (!$enabled) {
            if (current_user_can('manage_options')) {
                return '<div class="eep-chatbot-disabled-notice">' . esc_html__('AI Chatbot is disabled. Enable it in Settings > AI Chatbot.', 'nexdirectory') . '</div>';
            }
            return '';
        }

        $all_positions = method_exists($this, 'get_chatbot_position_options') ? (array) $this->get_chatbot_position_options('any') : ['bottom-right', 'bottom-left', 'inline'];
        $desktop_positions = method_exists($this, 'get_chatbot_position_options') ? (array) $this->get_chatbot_position_options('desktop') : ['bottom-right', 'bottom-left', 'inline'];
        $mobile_positions = method_exists($this, 'get_chatbot_position_options') ? (array) $this->get_chatbot_position_options('mobile') : ['bottom-right', 'bottom-left', 'inline'];

        $desktop_position = $atts['position_desktop'] !== ''
            ? sanitize_key((string) $atts['position_desktop'])
            : sanitize_key((string) get_option('eep_chatbot_position_desktop', 'bottom-right'));
        $mobile_position = $atts['position_mobile'] !== ''
            ? sanitize_key((string) $atts['position_mobile'])
            : sanitize_key((string) get_option('eep_chatbot_position_mobile', 'right-edge-center'));

        if ($atts['position'] !== '') {
            $position_override = sanitize_key((string) $atts['position']);
            $desktop_position = $position_override;
            $mobile_position = $position_override;
        }

        if (!in_array($desktop_position, $desktop_positions, true)) {
            $desktop_position = 'bottom-right';
        }
        if (!in_array($mobile_position, $mobile_positions, true)) {
            $mobile_position = 'right-edge-center';
        }

        $name = sanitize_text_field((string) ($atts['name'] !== '' ? $atts['name'] : get_option('eep_chatbot_name', __('Listing Assistant', 'nexdirectory'))));
        if ($name === '') {
            $name = __('Listing Assistant', 'nexdirectory');
        }
        $welcome = sanitize_text_field((string) ($atts['welcome'] !== '' ? $atts['welcome'] : get_option('eep_chatbot_welcome_text', __('Ask me about upcoming listings and I will recommend the best matches.', 'nexdirectory'))));
        $theme_color = sanitize_hex_color((string) ($atts['theme_color'] !== '' ? $atts['theme_color'] : get_option('eep_chatbot_theme_color', '#FF5E14')));
        $text_color = sanitize_hex_color((string) ($atts['text_color'] !== '' ? $atts['text_color'] : get_option('eep_chatbot_text_color', '#0B1221')));
        if (!$theme_color) {
            $theme_color = '#FF5E14';
        }
        if (!$text_color) {
            $text_color = '#0B1221';
        }

        return $this->render_frontend_template('event-chatbot.php', [
            'chatbot_name' => $name,
            'chatbot_welcome' => $welcome,
            'chatbot_position' => $desktop_position,
            'chatbot_position_desktop' => $desktop_position,
            'chatbot_position_mobile' => $mobile_position,
            'chatbot_theme_color' => $theme_color,
            'chatbot_text_color' => $text_color,
        ]);
    }


    public function render_whats_new($atts = []) {
        $atts = shortcode_atts([
            'posts_per_page' => 12,
        ], (array) $atts, 'eep_whats_new');

        $posts_per_page = max(1, (int) $atts['posts_per_page']);
        $paged = max(1, get_query_var('paged'), isset($_GET['paged']) ? absint((int) $_GET['paged']) : 1);
        $filters = [
            's' => '',
            'event_type' => '',
            'event_mode' => '',
            'event_price' => '',
            'event_tag' => '',
            'city' => '',
            'date_from' => '',
            'official' => '',
        ];

        $query = new WP_Query([
            'post_type' => self::POST_TYPE,
            'post_status' => 'publish',
            'posts_per_page' => $posts_per_page,
            'paged' => $paged,
            'orderby' => 'date',
            'order' => 'DESC',
        ]);
        $ui_labels = $this->get_site_profile_ui_labels();

        return $this->render_frontend_template('whats-new.php', [
            'posts_per_page' => $posts_per_page,
            'paged' => $paged,
            'filters' => $filters,
            'query' => $query,
            'ui_labels' => $ui_labels,
        ]);
    }


    private function resolve_profile_key_for_post($post_id, $fallback = '') {
        $post_id = absint((int) $post_id);
        $fallback = sanitize_key((string) $fallback);
        if ($fallback === '' && method_exists($this, 'get_site_profile_key')) {
            $fallback = sanitize_key((string) $this->get_site_profile_key());
        }

        $catalog = method_exists($this, 'get_site_profile_catalog') ? (array) $this->get_site_profile_catalog() : [];
        if ($fallback === '' || !isset($catalog[$fallback])) {
            $fallback = isset($catalog['events']) ? 'events' : $fallback;
        }
        if ($fallback === '') {
            $fallback = 'events';
        }

        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return $fallback;
        }

        // Enforce a single active template mode across the site.
        // This prevents cross-template wording/components from leaking via legacy per-post meta signals.
        if ($fallback !== '' && isset($catalog[$fallback])) {
            return $fallback;
        }

        $stored_profile = sanitize_key((string) get_post_meta($post_id, 'eep_listing_profile', true));
        $stored_profile_valid = $stored_profile !== '' && isset($catalog[$stored_profile]);
        if ($stored_profile_valid && $stored_profile !== 'events') {
            return $stored_profile;
        }

        $job_signal_keys = [
            'eep_job_type',
            'eep_job_work_mode',
            'eep_job_salary',
            'eep_job_apply_url',
            'eep_job_application_email',
            'eep_job_closing_date',
            'eep_job_department',
        ];
        $has_job_signal = false;
        foreach ($job_signal_keys as $meta_key) {
            if (trim((string) get_post_meta($post_id, $meta_key, true)) !== '') {
                $has_job_signal = true;
                break;
            }
        }
        if ($has_job_signal) {
            return 'job_board';
        }

        $market_signal_keys = [
            'eep_market_condition',
            'eep_market_price_type',
            'eep_market_seller_type',
            'eep_market_listing_status',
            'eep_market_brand',
            'eep_market_model',
            'eep_market_sku',
            'eep_market_delivery_methods',
            'eep_market_shipping_notes',
            'eep_market_expires_on',
        ];
        $has_marketplace_signal = false;
        foreach ($market_signal_keys as $meta_key) {
            if (trim((string) get_post_meta($post_id, $meta_key, true)) !== '') {
                $has_marketplace_signal = true;
                break;
            }
        }
        if ($has_marketplace_signal) {
            return 'marketplace';
        }

        $mode_terms = wp_get_post_terms($post_id, self::TAX_MODE, ['fields' => 'names']);
        if (is_wp_error($mode_terms)) {
            $mode_terms = [];
        }
        $mode_terms = array_filter(array_map(static function ($name) {
            return strtolower(trim((string) $name));
        }, (array) $mode_terms), static function ($name) {
            return $name !== '';
        });
        $mode_terms_joined = implode(' ', $mode_terms);

        $price_terms = wp_get_post_terms($post_id, self::TAX_PRICE, ['fields' => 'names']);
        if (is_wp_error($price_terms)) {
            $price_terms = [];
        }
        $price_terms = array_filter(array_map(static function ($name) {
            return strtolower(trim((string) $name));
        }, (array) $price_terms), static function ($name) {
            return $name !== '';
        });
        $price_terms_joined = implode(' ', $price_terms);

        $has_marketplace_term_signal = (
            strpos($mode_terms_joined, 'standard listing') !== false
            || strpos($mode_terms_joined, 'online listing') !== false
            || strpos($price_terms_joined, 'fixed price') !== false
            || strpos($price_terms_joined, 'negotiable') !== false
        );
        if ($has_marketplace_term_signal) {
            return 'marketplace';
        }

        $company_name = trim((string) get_post_meta($post_id, 'eep_company_name', true));
        $market_condition = trim((string) get_post_meta($post_id, 'eep_market_condition', true));
        $has_directory_term_signal = (
            strpos($mode_terms_joined, 'on-site service') !== false
            || strpos($mode_terms_joined, 'online service') !== false
            || strpos($price_terms_joined, 'paid service') !== false
            || strpos($price_terms_joined, 'free service') !== false
        );
        $has_event_mode_signal = (
            strpos($mode_terms_joined, 'in-person') !== false
            || strpos($mode_terms_joined, 'online stream') !== false
            || strpos($mode_terms_joined, 'video stream') !== false
            || strpos($mode_terms_joined, 'hybrid') !== false
        );
        if ($has_directory_term_signal) {
            return 'directory';
        }
        if ($company_name !== '' && !$has_job_signal && $market_condition === '' && !$has_event_mode_signal) {
            $recurrence_value = sanitize_key((string) get_post_meta($post_id, 'eep_recurrence', true));
            $has_event_specific_signal = (
                (string) get_post_meta($post_id, 'eep_official_event', true) === '1'
                || trim((string) get_post_meta($post_id, 'eep_event_stream_url', true)) !== ''
                || ($recurrence_value !== '' && $recurrence_value !== 'none')
            );
            if (!$has_event_specific_signal) {
                return 'directory';
            }
        }

        $event_stream_url = trim((string) get_post_meta($post_id, 'eep_event_stream_url', true));
        $recurrence = sanitize_key((string) get_post_meta($post_id, 'eep_recurrence', true));
        $sessions_json = (string) get_post_meta($post_id, 'eep_sessions', true);
        $sessions = json_decode($sessions_json, true);
        $has_sessions = is_array($sessions) && !empty($sessions);
        $official_flag = (string) get_post_meta($post_id, 'eep_official_event', true) === '1';
        $has_event_specific_signal = ($event_stream_url !== '' || ($recurrence !== '' && $recurrence !== 'none') || $has_sessions || $official_flag);

        if ($stored_profile_valid) {
            if ($stored_profile === 'events' && $fallback !== 'events') {
                return $fallback;
            }
            return $stored_profile;
        }

        if ($fallback !== 'events') {
            return $fallback;
        }

        if ($has_event_specific_signal) {
            return 'events';
        }

        return $fallback;
    }


    private function persist_listing_profile_for_post($post_id, $profile_key) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return;
        }
        $profile_key = sanitize_key((string) $profile_key);
        $catalog = method_exists($this, 'get_site_profile_catalog') ? (array) $this->get_site_profile_catalog() : [];
        if ($profile_key === '' || !isset($catalog[$profile_key])) {
            $profile_key = 'events';
        }
        update_post_meta($post_id, 'eep_listing_profile', $profile_key);
    }


    private function user_is_account_app_non_owner_in_single_org_mode($user_id = 0) {
        $user_id = absint((int) $user_id);
        if ($user_id <= 0 && is_user_logged_in()) {
            $user_id = (int) get_current_user_id();
        }
        if ($user_id <= 0) {
            return false;
        }

        $platform_mode = sanitize_key((string) get_option('eep_platform_mode', 'user_generated'));
        if ($platform_mode !== 'single_organisation') {
            return false;
        }
        if (user_can($user_id, 'manage_options')) {
            return false;
        }

        $owned_count = (int) count_user_posts($user_id, self::POST_TYPE, false);
        return $owned_count <= 0;
    }


    private function get_account_app_default_tab($user_id = 0, array $allowed_tabs = []) {
        $allowed_tabs = array_values(array_filter(array_map('sanitize_key', $allowed_tabs)));
        if ($this->user_is_account_app_non_owner_in_single_org_mode($user_id) && in_array('saved', $allowed_tabs, true)) {
            return 'saved';
        }
        if (in_array('account', $allowed_tabs, true)) {
            return 'account';
        }
        return isset($allowed_tabs[0]) ? (string) $allowed_tabs[0] : 'account';
    }


    private function get_account_app_allowed_tabs($user_id = 0) {
        $tabs = ['account', 'analytics', 'listings', 'submit', 'inbox', 'saved'];
        $user_id = absint((int) $user_id);
        if ($user_id <= 0 && is_user_logged_in()) {
            $user_id = (int) get_current_user_id();
        }

        $platform_mode = sanitize_key((string) get_option('eep_platform_mode', 'user_generated'));
        $can_manage = $user_id > 0 ? user_can($user_id, 'manage_options') : current_user_can('manage_options');
        if ($platform_mode === 'single_organisation' && !$can_manage) {
            $tabs = array_values(array_diff($tabs, ['listings', 'submit']));
            if ($this->user_is_account_app_non_owner_in_single_org_mode($user_id)) {
                $tabs = array_values(array_diff($tabs, ['analytics']));
            }
        }

        return $tabs;
    }


    private function sanitize_account_app_tab($tab, $user_id = 0) {
        $tab = sanitize_key((string) $tab);
        $allowed_tabs = $this->get_account_app_allowed_tabs($user_id);
        $default_tab = $this->get_account_app_default_tab($user_id, $allowed_tabs);
        if (!in_array($tab, $allowed_tabs, true)) {
            $tab = $default_tab;
        }
        return $tab;
    }


    private function sanitize_account_app_listings_subtab($subtab) {
        $subtab = sanitize_key((string) $subtab);
        if (!in_array($subtab, ['manage', 'boosts', 'webapp'], true)) {
            $subtab = 'manage';
        }
        return $subtab;
    }


    private function render_account_app_template(array $options = []) {
        $options = wp_parse_args($options, [
            'initial_tab' => 'account',
            'initial_listings_subtab' => 'manage',
            'show_nav' => '',
            'posts_per_page' => 30,
            'history_limit' => 120,
            'tickets_limit' => 200,
            'notifications_limit' => 80,
            'analytics_days' => 30,
            'singleton' => true,
            'login_message' => __('Please log in to manage your account.', 'nexdirectory'),
        ]);

        static $account_app_rendered_once = false;

        $initial_tab = $this->sanitize_account_app_tab($options['initial_tab']);
        $initial_listings_subtab = $this->sanitize_account_app_listings_subtab($options['initial_listings_subtab']);
        $requested_listings_subtab = isset($_GET['listings_subtab']) ? sanitize_key((string) wp_unslash($_GET['listings_subtab'])) : '';
        if ($requested_listings_subtab === '' && isset($_GET['eep_listings_subtab'])) {
            $requested_listings_subtab = sanitize_key((string) wp_unslash($_GET['eep_listings_subtab']));
        }
        if ($requested_listings_subtab !== '') {
            $initial_listings_subtab = $this->sanitize_account_app_listings_subtab($requested_listings_subtab);
            $initial_tab = 'listings';
        }
        $show_nav_setting = (string) $options['show_nav'];
        $singleton = !empty($options['singleton']);
        $default_show_nav = (string) get_option('eep_default_submit_show_nav', '1') === '1';
        $show_nav = $show_nav_setting === '' ? $default_show_nav : ($show_nav_setting !== 'no');
        $login_message = sanitize_text_field((string) $options['login_message']);
        if ($login_message === '') {
            $login_message = __('Please log in to manage your account.', 'nexdirectory');
        }

        if (!is_user_logged_in()) {
            return $this->redirect_to_login_screen($login_message);
        }

        if ($singleton && $account_app_rendered_once) {
            return '';
        }
        if ($singleton) {
            $account_app_rendered_once = true;
        }

        $current_user_id = (int) get_current_user_id();
        $allowed_tabs = $this->get_account_app_allowed_tabs($current_user_id);
        $initial_tab = $this->sanitize_account_app_tab($initial_tab, $current_user_id);
        if ($initial_tab === 'listings' && !in_array('listings', $allowed_tabs, true)) {
            $initial_listings_subtab = 'manage';
        }
        $ui_labels = $this->get_site_profile_ui_labels();
        $profile_key = sanitize_key((string) ($ui_labels['profile_key'] ?? ''));
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $singular_lower = sanitize_text_field((string) ($ui_labels['singular_lower'] ?? __('event', 'nexdirectory')));

        $events_url = $this->get_platform_page_url(
            'eep_page_events_url',
            $this->get_profile_route_slugs('events', $profile_key),
            $this->get_profile_route_default_url('events', $profile_key)
        );
        $submit_event_url = $this->get_platform_page_url(
            'eep_page_submit_event_url',
            $this->get_profile_route_slugs('submit', $profile_key),
            $this->get_profile_route_default_url('submit', $profile_key)
        );
        $my_events_url = $this->get_platform_page_url(
            'eep_page_my_events_url',
            $this->get_profile_route_slugs('my_items', $profile_key),
            $this->get_profile_route_default_url('my_items', $profile_key)
        );
        $my_tickets_url = $this->get_platform_page_url(
            'eep_page_my_tickets_url',
            $this->get_profile_route_slugs('attending', $profile_key),
            $this->get_profile_route_default_url('attending', $profile_key)
        );

        $user = wp_get_current_user();
        $account_description = sanitize_text_field((string) ($ui_labels['account_description'] ?? __('Update your profile and manage your listing activity.', 'nexdirectory')));
        $gdpr_marketing_opt_in = $this->user_allows_organizer_communications((int) $user->ID);
        $event_messages_enabled = $this->user_allows_event_messages((int) $user->ID);
        $gdpr_terms_accepted_at = absint((int) get_user_meta((int) $user->ID, 'eep_gdpr_terms_accepted_at', true));
        $gdpr_terms_accepted_label = $gdpr_terms_accepted_at > 0
            ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), $gdpr_terms_accepted_at)
            : '';

        $days = isset($_GET['eep_owner_days']) ? absint((int) wp_unslash($_GET['eep_owner_days'])) : absint((int) $options['analytics_days']);
        if (!in_array($days, [7, 30, 90, 365], true)) {
            $days = 30;
        }
        $analytics_title = sanitize_text_field((string) ($ui_labels['owner_analytics_title'] ?? __('Analytics', 'nexdirectory')));
        $analytics_description = sanitize_text_field((string) ($ui_labels['owner_analytics_description'] ?? __('Review listing views, clicks, enquiries, and discovery performance.', 'nexdirectory')));
        $report = [];
        if (!function_exists('epw_fs') || epw_fs()->can_use_premium_code__premium_only()) {
            $report = $this->get_owner_analytics_report($current_user_id, $days);
        }

        $posts_per_page = max(1, (int) $options['posts_per_page']);
        $owned_event_ids = get_posts([
            'post_type' => self::POST_TYPE,
            'author' => $current_user_id,
            'post_status' => ['pending', 'publish', 'draft'],
            'posts_per_page' => -1,
            'fields' => 'ids',
            'orderby' => 'date',
            'order' => 'DESC',
            'no_found_rows' => true,
            'suppress_filters' => true,
        ]);
        $team_event_ids = $this->get_event_ids_for_team_user($current_user_id);
        $visible_event_ids = array_values(array_unique(array_map('absint', array_merge(
            is_array($owned_event_ids) ? $owned_event_ids : [],
            is_array($team_event_ids) ? $team_event_ids : []
        ))));
        $query_args = [
            'post_type' => self::POST_TYPE,
            'post_status' => ['pending', 'publish', 'draft'],
            'posts_per_page' => $posts_per_page,
            'orderby' => 'date',
            'order' => 'DESC',
        ];
        if (!empty($visible_event_ids)) {
            $query_args['post__in'] = $visible_event_ids;
        } else {
            $query_args['post__in'] = [0];
        }
        $query = new WP_Query($query_args);
        $open_event_id = isset($_GET['event_id']) ? absint((int) wp_unslash($_GET['event_id'])) : 0;
        if ($open_event_id > 0 && !$this->user_can_edit_event($open_event_id, $current_user_id)) {
            $open_event_id = 0;
        }
        $selected_webapp_event_id = isset($_GET['webapp_event']) ? absint((int) wp_unslash($_GET['webapp_event'])) : 0;
        if ($selected_webapp_event_id > 0 && !$this->event_webapp_user_can_manage_event($selected_webapp_event_id, $current_user_id)) {
            $selected_webapp_event_id = 0;
        }
        $attendees_event_id = isset($_GET['attendees_event']) ? absint((int) wp_unslash($_GET['attendees_event'])) : 0;
        $attendees_panel = null;
        if ($attendees_event_id > 0) {
            $owner_id = (int) get_post_field('post_author', $attendees_event_id);
            $can_view_attendees = get_post_type($attendees_event_id) === self::POST_TYPE
                && (
                    $owner_id === $current_user_id
                    || $this->user_can_moderate_events()
                    || $this->user_has_event_team_role($attendees_event_id, $current_user_id, ['owner', 'editor', 'support'], ['active'], 'website')
                );
            if ($can_view_attendees) {
                $attendee_rows = $this->get_event_attendee_rows($attendees_event_id);
                $communication_allowed_count = count(array_filter($attendee_rows, static function ($row) {
                    return (string) ($row['communication_opt_in'] ?? '0') === '1';
                }));
                $attendees_panel = [
                    'event_id' => $attendees_event_id,
                    'event_title' => (string) get_the_title($attendees_event_id),
                    'rows' => $attendee_rows,
                    'yes_count' => count(array_filter($attendee_rows, static function ($row) {
                        return (string) ($row['status'] ?? '') === 'yes';
                    })),
                    'waitlist_count' => count(array_filter($attendee_rows, static function ($row) {
                        return (string) ($row['status'] ?? '') === 'waitlist';
                    })),
                    'communication_allowed_count' => $communication_allowed_count,
                    'communication_blocked_count' => max(0, count($attendee_rows) - $communication_allowed_count),
                    'export_action' => admin_url('admin-post.php'),
                    'export_nonce' => wp_create_nonce('eep_export_event_attendees_' . $attendees_event_id),
                    'email_action' => admin_url('admin-post.php'),
                    'email_nonce' => wp_create_nonce('eep_email_event_attendees_' . $attendees_event_id),
                ];
            } else {
                $attendees_event_id = 0;
            }
        }

        $boosts_enabled = method_exists($this, 'is_boost_enabled') ? (bool) $this->is_boost_enabled() : false;
        $history_limit = max(1, min(400, (int) $options['history_limit']));
        $boost_history_rows = [];
        if ($boosts_enabled && method_exists($this, 'get_boost_order_history_for_user')) {
            $boost_history_rows = (array) $this->get_boost_order_history_for_user($current_user_id, $history_limit);
        }
        $boost_gateway_stripe_enabled = $boosts_enabled && (string) get_option('eep_boost_stripe_enabled', '0') === '1';
        $boost_gateway_paypal_enabled = $boosts_enabled && (string) get_option('eep_boost_paypal_enabled', '0') === '1';

        $webapp_event_options = [];
        if ($is_events_profile && !empty($visible_event_ids)) {
            $webapp_posts = get_posts([
                'post_type' => self::POST_TYPE,
                'post_status' => ['pending', 'publish', 'draft'],
                'post__in' => $visible_event_ids,
                'posts_per_page' => -1,
                'orderby' => 'date',
                'order' => 'DESC',
                'suppress_filters' => true,
            ]);
            foreach ((array) $webapp_posts as $webapp_post) {
                $webapp_post_id = 0;
                if ($webapp_post instanceof WP_Post) {
                    $webapp_post_id = absint((int) $webapp_post->ID);
                } elseif (is_numeric($webapp_post)) {
                    $webapp_post_id = absint((int) $webapp_post);
                } elseif (is_array($webapp_post) && isset($webapp_post['ID'])) {
                    $webapp_post_id = absint((int) $webapp_post['ID']);
                }
                if ($webapp_post_id <= 0) {
                    continue;
                }
                $webapp_event_options[] = [
                    'id' => $webapp_post_id,
                    'title' => sanitize_text_field((string) get_the_title($webapp_post_id)),
                    'status' => sanitize_key((string) get_post_status($webapp_post_id)),
                    'date_label' => sanitize_text_field((string) $this->format_event_date($webapp_post_id)),
                ];
            }
        }
        if ($is_events_profile && empty($webapp_event_options) && $query instanceof WP_Query && !empty($query->posts)) {
            foreach ((array) $query->posts as $webapp_post) {
                $webapp_post_id = 0;
                if ($webapp_post instanceof WP_Post) {
                    $webapp_post_id = absint((int) $webapp_post->ID);
                } elseif (is_numeric($webapp_post)) {
                    $webapp_post_id = absint((int) $webapp_post);
                } elseif (is_array($webapp_post) && isset($webapp_post['ID'])) {
                    $webapp_post_id = absint((int) $webapp_post['ID']);
                }
                if ($webapp_post_id <= 0) {
                    continue;
                }
                $webapp_event_options[] = [
                    'id' => $webapp_post_id,
                    'title' => sanitize_text_field((string) get_the_title($webapp_post_id)),
                    'status' => sanitize_key((string) get_post_status($webapp_post_id)),
                    'date_label' => sanitize_text_field((string) $this->format_event_date($webapp_post_id)),
                ];
            }
        }
        if ($selected_webapp_event_id <= 0 && !empty($webapp_event_options)) {
            $selected_webapp_event_id = absint((int) ($webapp_event_options[0]['id'] ?? 0));
        }
        if ($selected_webapp_event_id > 0 && !$this->event_webapp_user_can_manage_event($selected_webapp_event_id, $current_user_id)) {
            $selected_webapp_event_id = 0;
        }
        $webapp_prefill = $this->build_submit_form_prefill(
            $selected_webapp_event_id,
            $this->resolve_profile_key_for_post($selected_webapp_event_id, $profile_key)
        );
        $webapp_manage_page_url = eep_add_query_arg([
            'eep_tab' => 'listings',
            'listings_subtab' => 'webapp',
        ], $my_events_url) . '#listings';
        $webapp_selected_manage_url = $selected_webapp_event_id > 0
            ? eep_add_query_arg([
                'eep_tab' => 'listings',
                'listings_subtab' => 'webapp',
                'webapp_event' => $selected_webapp_event_id,
            ], $my_events_url) . '#listings'
            : $webapp_manage_page_url;
        $selected_event_webapp_url = $selected_webapp_event_id > 0 ? (string) $this->get_event_webapp_page_url($selected_webapp_event_id) : '';
        $selected_event_manifest_url = $selected_webapp_event_id > 0 ? (string) $this->get_event_webapp_manifest_url($selected_webapp_event_id) : '';
        $selected_event_service_worker_url = $selected_webapp_event_id > 0 ? (string) $this->get_event_webapp_service_worker_url($selected_webapp_event_id) : '';
        $selected_event_service_worker_scope = $selected_webapp_event_id > 0 ? (string) $this->get_event_webapp_service_worker_scope($selected_webapp_event_id) : '';
        $webapp_management_streams = [
            'chat' => [],
            'qa' => [],
            'polls' => [],
            'notifications' => [],
        ];
        if ($selected_webapp_event_id > 0) {
            $webapp_management_streams['chat'] = array_values(array_reverse(array_slice(
                $this->get_event_webapp_chat_response_rows($selected_webapp_event_id, $current_user_id, true),
                -120
            )));
            $webapp_management_streams['qa'] = array_values(array_slice(
                $this->get_event_webapp_qa_response_rows($selected_webapp_event_id, $current_user_id, true),
                0,
                120
            ));
            $webapp_management_streams['polls'] = array_values(array_slice(
                $this->get_event_webapp_poll_response_rows($selected_webapp_event_id, $current_user_id, true, true),
                0,
                120
            ));
            $webapp_management_streams['notifications'] = array_values(array_slice(
                $this->get_event_webapp_notification_rows($selected_webapp_event_id),
                0,
                120
            ));
        }
        if ($is_events_profile && $initial_listings_subtab === 'webapp') {
            $initial_tab = 'listings';
        }

        $force_new_submit = sanitize_key((string) wp_unslash($_GET['eep_new'] ?? '')) === '1';
        $edit_event_id = $force_new_submit
            ? 0
            : (isset($_GET['edit_event_id']) ? absint((int) wp_unslash($_GET['edit_event_id'])) : 0);
        $resolved_profile_key = $this->resolve_profile_key_for_post($edit_event_id, $profile_key);
        wp_enqueue_style('eep-leaflet');
        wp_enqueue_script('eep-leaflet');
        $use_rich_editor = (string) get_option('eep_use_rich_editor', '1') === '1';
        if ($use_rich_editor && function_exists('wp_enqueue_editor')) {
            wp_enqueue_editor();
        }
        $prefill = $this->build_submit_form_prefill($edit_event_id, $resolved_profile_key);
        if ($edit_event_id > 0 && empty($prefill['is_edit'])) {
            $locked_demo_edit = is_callable([$this, 'is_demo_event_edit_locked'])
                ? (bool) $this->is_demo_event_edit_locked($edit_event_id, $current_user_id)
                : false;
            $this->redirect_with_message(
                $my_events_url,
                $locked_demo_edit
                    ? __('This demo event is read-only on the free plan. Duplicate it to create an editable copy.', 'nexdirectory')
                    : sprintf(__('You can only edit your own %s.', 'nexdirectory'), $singular_lower),
                'error'
            );
        }
        if (!empty($prefill['is_edit']) && in_array('submit', $allowed_tabs, true)) {
            $initial_tab = 'submit';
        }
        $custom_fields_enabled = (string) get_option('eep_enable_custom_submission_fields', '0') === '1';
        $per_event_email_templates_enabled = (string) get_option('eep_enable_per_event_email_templates', '0') === '1';
        $organizer_event_seo_enabled = (string) get_option('eep_enable_event_seo_fields', '1') === '1';
        $custom_form_fields = $custom_fields_enabled
            ? (is_array($prefill['custom_form_fields'] ?? null) ? (array) $prefill['custom_form_fields'] : [])
            : [];
        if (!$custom_fields_enabled) {
            $prefill['custom_form_fields'] = [];
            $prefill['custom_fields_values'] = [];
        }
        $custom_taxonomy_fields = [];
        foreach ($this->get_custom_taxonomy_definitions_for_submit() as $taxonomy_row) {
            $taxonomy_slug = sanitize_key((string) ($taxonomy_row['slug'] ?? ''));
            if ($taxonomy_slug === '' || !taxonomy_exists($taxonomy_slug)) {
                continue;
            }
            $term_rows = get_terms([
                'taxonomy' => $taxonomy_slug,
                'hide_empty' => false,
                'orderby' => 'name',
                'order' => 'ASC',
            ]);
            if (is_wp_error($term_rows)) {
                continue;
            }
            $selected_ids = [];
            if (is_array($prefill['custom_taxonomy_terms'] ?? null)) {
                $selected_ids = array_values(array_filter(array_map('absint', (array) ($prefill['custom_taxonomy_terms'][$taxonomy_slug] ?? []))));
            }
            $custom_taxonomy_fields[] = [
                'slug' => $taxonomy_slug,
                'label' => sanitize_text_field((string) ($taxonomy_row['label'] ?? $taxonomy_slug)),
                'hierarchical' => (string) ($taxonomy_row['hierarchical'] ?? '1') === '1',
                'terms' => is_array($term_rows) ? $term_rows : [],
                'selected_ids' => $selected_ids,
            ];
        }
        $upload_limits = $this->get_upload_limits();
        $excerpt_max_chars = max(80, min(1000, (int) get_option('eep_excerpt_max_chars', 220)));
        $classification_labels = $this->get_classification_labels();
        $official_pending_note = sprintf(
            __('Official %s classification is reviewed by admins before it appears publicly.', 'nexdirectory'),
            strtolower($singular_label)
        );
        $event_completion_missing = [];
        if (!empty($prefill['is_edit']) && !empty($prefill['edit_event_id'])) {
            $event_completion_missing = $this->get_event_optional_sections_missing((int) $prefill['edit_event_id'], $resolved_profile_key);
        }
        $team_access_events = $this->get_manageable_team_events_for_user($current_user_id, 400);
        $selected_team_access_event_id = isset($_GET['team_event']) ? absint((int) wp_unslash($_GET['team_event'])) : 0;
        $team_access_event_ids = array_values(array_filter(array_map(static function ($row) {
            return absint((int) ($row['id'] ?? 0));
        }, (array) $team_access_events)));
        if ($selected_team_access_event_id > 0 && !in_array($selected_team_access_event_id, $team_access_event_ids, true)) {
            $selected_team_access_event_id = 0;
        }
        if ($selected_team_access_event_id <= 0 && !empty($team_access_event_ids)) {
            $selected_team_access_event_id = (int) $team_access_event_ids[0];
        }

        $team_access_manage_action = admin_url('admin-post.php');
        $team_access_page_url = is_string($my_events_url) && $my_events_url !== '' ? $my_events_url : home_url('/');
        $team_access_base_url = eep_add_query_arg([
            'eep_tab' => 'account',
            'account_subtab' => 'team-access',
        ], $team_access_page_url) . '#account';
        $team_access_selected_url = $selected_team_access_event_id > 0
            ? eep_add_query_arg([
                'eep_tab' => 'account',
                'account_subtab' => 'team-access',
                'team_event' => $selected_team_access_event_id,
            ], $team_access_page_url) . '#account'
            : $team_access_base_url;

        $team_access_members = [];
        $team_access_followers = [];
        $team_access_can_manage_selected = false;
        if ($selected_team_access_event_id > 0 && $this->can_manage_event_team($selected_team_access_event_id, $current_user_id)) {
            $team_access_can_manage_selected = true;
            $team_access_members = $this->get_event_team_members_for_template($selected_team_access_event_id);
            $team_access_followers = $this->get_event_team_follower_candidates($selected_team_access_event_id);
        }

        $notifications_limit = max(1, min(300, (int) $options['notifications_limit']));
        $notifications_title = __('Inbox', 'nexdirectory');
        $notifications_description = __('View and reply to messages from attendees, organizers, and listing visitors.', 'nexdirectory');
        $threads = [];
        $active_thread_id = 0;
        $active_thread = [];
        $active_messages = [];
        $active_subject = '';
        $active_event_title = '';
        $active_event_url = '';
        $active_other_name = __('Member', 'nexdirectory');
        $active_other_id = 0;
        $reply_subject = '';
        $unread_count = 0;

        if ($this->user_can_access_platform($current_user_id)) {
            $requested_thread_id = isset($_GET['thread']) ? absint((int) wp_unslash($_GET['thread'])) : 0;
            $highlight_message_id = isset($_GET['message']) ? absint((int) wp_unslash($_GET['message'])) : 0;

            if ($requested_thread_id > 0 && $this->get_inbox_thread_for_user($requested_thread_id, $current_user_id)) {
                $this->mark_inbox_thread_read_for_user($requested_thread_id, $current_user_id);
            }

            $threads = $this->get_inbox_threads_for_user($current_user_id, $notifications_limit);
            if ($requested_thread_id > 0) {
                foreach ($threads as $thread_row) {
                    if ((int) ($thread_row['thread_id'] ?? 0) === $requested_thread_id) {
                        $active_thread_id = $requested_thread_id;
                        break;
                    }
                }
            }
            if ($active_thread_id <= 0 && !empty($threads)) {
                $active_thread_id = absint((int) ($threads[0]['thread_id'] ?? 0));
            }

            if ($active_thread_id > 0) {
                $active_thread = (array) $this->get_inbox_thread_for_user($active_thread_id, $current_user_id);
                if (!empty($active_thread)) {
                    $this->mark_inbox_thread_read_for_user($active_thread_id, $current_user_id);
                    $active_post_id = absint((int) ($active_thread['post_id'] ?? 0));
                    if ($active_post_id > 0) {
                        $active_event_title = sanitize_text_field((string) get_the_title($active_post_id));
                        $active_event_url = (string) get_permalink($active_post_id);
                    }
                    $active_subject = sanitize_text_field((string) ($active_thread['subject'] ?? ''));
                    if ($active_subject === '' && $active_event_title !== '') {
                        $active_subject = sprintf(__('Message about %s', 'nexdirectory'), $active_event_title);
                    }
                    $participant_a = absint((int) ($active_thread['participant_a'] ?? 0));
                    $participant_b = absint((int) ($active_thread['participant_b'] ?? 0));
                    $active_other_id = $participant_a === $current_user_id ? $participant_b : $participant_a;
                    if ($active_other_id > 0) {
                        $other_user = get_user_by('id', $active_other_id);
                        if ($other_user instanceof WP_User) {
                            $active_other_name = sanitize_text_field((string) ($other_user->display_name ?: $other_user->user_login));
                        }
                    }

                    $raw_messages = $this->get_inbox_messages_for_thread($active_thread_id, 500);
                    $sender_ids = [];
                    foreach ($raw_messages as $raw_message) {
                        $sender_id = absint((int) ($raw_message['sender_id'] ?? 0));
                        if ($sender_id > 0) {
                            $sender_ids[] = $sender_id;
                        }
                    }
                    $sender_ids = array_values(array_unique($sender_ids));
                    $sender_map = [];
                    if (!empty($sender_ids)) {
                        $sender_users = get_users([
                            'include' => $sender_ids,
                            'fields' => ['ID', 'display_name', 'user_login'],
                        ]);
                        foreach ((array) $sender_users as $sender_user) {
                            if (!$sender_user instanceof WP_User) {
                                continue;
                            }
                            $sender_map[(int) $sender_user->ID] = sanitize_text_field((string) ($sender_user->display_name ?: $sender_user->user_login));
                        }
                    }
                    foreach ($raw_messages as $raw_message) {
                        $message_id = absint((int) ($raw_message['id'] ?? 0));
                        if ($message_id <= 0) {
                            continue;
                        }
                        $sender_id = absint((int) ($raw_message['sender_id'] ?? 0));
                        $created_ts = strtotime((string) ($raw_message['created_at'] ?? ''));
                        $message_subject = sanitize_text_field((string) ($raw_message['subject'] ?? ''));
                        if ($message_subject === '') {
                            $message_subject = $active_subject;
                        }
                        $active_messages[] = [
                            'message_id' => $message_id,
                            'sender_id' => $sender_id,
                            'sender_name' => (string) ($sender_map[$sender_id] ?? __('Member', 'nexdirectory')),
                            'subject' => $message_subject,
                            'body' => eep_sanitize_textarea((string) ($raw_message['body'] ?? '')),
                            'attachment_url' => esc_url_raw((string) ($raw_message['attachment_url'] ?? '')),
                            'created_label' => $created_ts ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), $created_ts) : '',
                            'is_mine' => $sender_id === $current_user_id,
                            'is_highlight' => $highlight_message_id > 0 && $highlight_message_id === $message_id,
                        ];
                    }
                    $reply_subject = $active_subject;
                }
            }

            $unread_count = $this->get_inbox_unread_count_for_user($current_user_id);
        }

        $tickets_limit = max(1, (int) $options['tickets_limit']);
        $saved_rows = $this->get_rsvp_rows_for_user($current_user_id, $tickets_limit);

        $output = $this->render_frontend_template('account-app.php', [
            'available_tabs' => $allowed_tabs,
            'initial_tab' => $initial_tab,
            'initial_listings_subtab' => $initial_listings_subtab,
            'show_nav' => $show_nav,
            'ui_labels' => $ui_labels,
            'profile_key' => $profile_key,
            'user' => $user,
            'account_description' => $account_description,
            'gdpr_marketing_opt_in' => $gdpr_marketing_opt_in,
            'event_messages_enabled' => $event_messages_enabled,
            'gdpr_terms_accepted_label' => $gdpr_terms_accepted_label,
            'analytics_title' => $analytics_title,
            'analytics_description' => $analytics_description,
            'days' => $days,
            'report' => $report,
            'events_url' => $events_url,
            'query' => $query,
            'open_event_id' => $open_event_id,
            'submit_event_url' => $submit_event_url,
            'my_events_url' => $my_events_url,
            'my_tickets_url' => $my_tickets_url,
            'attendees_event_id' => $attendees_event_id,
            'attendees_panel' => $attendees_panel,
            'boosts_enabled' => $boosts_enabled,
            'boost_history_rows' => $boost_history_rows,
            'boost_gateway_stripe_enabled' => $boost_gateway_stripe_enabled,
            'boost_gateway_paypal_enabled' => $boost_gateway_paypal_enabled,
            'use_rich_editor' => $use_rich_editor,
            'submit_recaptcha_required' => $this->should_enforce_submit_form_recaptcha(),
            'prefill' => $prefill,
            'custom_form_fields' => $custom_form_fields,
            'custom_field_types' => $this->get_custom_field_types(),
            'custom_fields_enabled' => $custom_fields_enabled,
            'per_event_email_templates_enabled' => $per_event_email_templates_enabled,
            'organizer_event_seo_enabled' => $organizer_event_seo_enabled,
            'upload_limits' => $upload_limits,
            'excerpt_max_chars' => $excerpt_max_chars,
            'custom_taxonomy_fields' => $custom_taxonomy_fields,
            'classification_labels' => $classification_labels,
            'official_pending_note' => $official_pending_note,
            'event_completion_missing' => $event_completion_missing,
            'team_access_events' => $team_access_events,
            'selected_team_access_event_id' => $selected_team_access_event_id,
            'team_access_members' => $team_access_members,
            'team_access_followers' => $team_access_followers,
            'team_access_can_manage_selected' => $team_access_can_manage_selected,
            'team_access_manage_action' => $team_access_manage_action,
            'team_access_base_url' => $team_access_base_url,
            'team_access_selected_url' => $team_access_selected_url,
            'notifications_title' => $notifications_title,
            'notifications_description' => $notifications_description,
            'threads' => $threads,
            'active_thread_id' => $active_thread_id,
            'active_thread' => $active_thread,
            'active_messages' => $active_messages,
            'active_subject' => $active_subject,
            'active_event_title' => $active_event_title,
            'active_event_url' => $active_event_url,
            'active_other_name' => $active_other_name,
            'active_other_id' => $active_other_id,
            'reply_action' => eep_add_query_arg('eep_tab', 'inbox', admin_url('admin-post.php')),
            'reply_nonce' => $active_thread_id > 0 ? wp_create_nonce('eep_inbox_reply_' . $active_thread_id) : '',
            'reply_subject' => $reply_subject,
            'unread_count' => $unread_count,
            'saved_rows' => $saved_rows,
            'webapp_event_options' => $webapp_event_options,
            'selected_webapp_event_id' => $selected_webapp_event_id,
            'webapp_prefill' => $webapp_prefill,
            'webapp_manage_page_url' => $webapp_manage_page_url,
            'webapp_selected_manage_url' => $webapp_selected_manage_url,
            'selected_event_webapp_url' => $selected_event_webapp_url,
            'selected_event_manifest_url' => $selected_event_manifest_url,
            'selected_event_service_worker_url' => $selected_event_service_worker_url,
            'selected_event_service_worker_scope' => $selected_event_service_worker_scope,
            'webapp_management_streams' => $webapp_management_streams,
        ]);
        wp_reset_postdata();
        return $output;
    }


    public function render_submit_event($atts = []) {
        $atts = shortcode_atts([
            'show_nav' => '',
        ], (array) $atts, 'eep_submit_event');
        $ui_labels = $this->get_site_profile_ui_labels();
        $singular_lower = sanitize_text_field((string) ($ui_labels['singular_lower'] ?? __('event', 'nexdirectory')));

        return $this->render_account_app_template([
            'initial_tab' => 'submit',
            'show_nav' => (string) $atts['show_nav'],
            'login_message' => sprintf(__('Please log in to submit a %s.', 'nexdirectory'), $singular_lower),
        ]);
    }


    private function get_event_team_meta_key() {
        return 'eep_event_team_members';
    }


    private function normalize_event_team_role($role) {
        $role = sanitize_key((string) $role);
        return in_array($role, ['owner', 'editor', 'app_moderator', 'support'], true) ? $role : 'support';
    }


    private function normalize_event_team_status($status, $default = 'invited') {
        $default = sanitize_key((string) $default);
        if ($default !== '' && !in_array($default, ['invited', 'active', 'disabled'], true)) {
            $default = 'invited';
        }
        $status = sanitize_key((string) $status);
        if (in_array($status, ['invited', 'active', 'disabled'], true)) {
            return $status;
        }
        return $default;
    }


    private function get_event_team_role_label($role) {
        $role = $this->normalize_event_team_role($role);
        if ($role === 'owner') {
            return __('Owner', 'nexdirectory');
        }
        if ($role === 'editor') {
            return __('Editor', 'nexdirectory');
        }
        if ($role === 'app_moderator') {
            return __('App Moderator', 'nexdirectory');
        }
        return __('Support', 'nexdirectory');
    }


    private function get_event_team_status_label($status) {
        $status = $this->normalize_event_team_status($status, 'invited');
        if ($status === 'active') {
            return __('Active', 'nexdirectory');
        }
        if ($status === 'disabled') {
            return __('Disabled', 'nexdirectory');
        }
        return __('Invited', 'nexdirectory');
    }


    private function normalize_event_team_scope_flag($value, $default = '1') {
        $default = (string) $default === '0' ? '0' : '1';
        $raw = is_string($value) || is_numeric($value) ? strtolower(trim((string) $value)) : '';
        if ($raw === '') {
            return $default;
        }
        if (in_array($raw, ['1', 'true', 'yes', 'on'], true)) {
            return '1';
        }
        if (in_array($raw, ['0', 'false', 'no', 'off'], true)) {
            return '0';
        }
        return $default;
    }


    private function get_event_team_scope_flags(array $row = []) {
        $legacy_scopes = isset($row['scopes']) ? $row['scopes'] : null;
        $legacy_is_array = is_array($legacy_scopes);
        $legacy_website = $legacy_is_array ? ($legacy_scopes['website'] ?? null) : null;
        $legacy_webapp = $legacy_is_array ? ($legacy_scopes['webapp'] ?? null) : null;

        $scope_website = $this->normalize_event_team_scope_flag(
            $row['scope_website'] ?? $legacy_website,
            '1'
        );
        $scope_webapp = $this->normalize_event_team_scope_flag(
            $row['scope_webapp'] ?? $legacy_webapp,
            '1'
        );

        if ($scope_website === '0' && $scope_webapp === '0') {
            $scope_website = '1';
        }

        return [
            'website' => $scope_website,
            'webapp' => $scope_webapp,
        ];
    }


    private function event_team_member_has_scope(array $row, $scope = 'website') {
        $scope = sanitize_key((string) $scope);
        if (!in_array($scope, ['website', 'webapp'], true)) {
            $scope = 'website';
        }
        $flags = $this->get_event_team_scope_flags($row);
        return (string) ($flags[$scope] ?? '0') === '1';
    }


    private function get_event_team_scope_label(array $row) {
        $flags = $this->get_event_team_scope_flags($row);
        if ($flags['website'] === '1' && $flags['webapp'] === '1') {
            return __('Website + Web App', 'nexdirectory');
        }
        if ($flags['webapp'] === '1') {
            return __('Web App Only', 'nexdirectory');
        }
        return __('Website Only', 'nexdirectory');
    }


    private function get_safe_redirect_url($redirect_to, $fallback = '') {
        $fallback = esc_url_raw((string) $fallback);
        if ($fallback === '') {
            $fallback = home_url('/');
        }
        $redirect_to = esc_url_raw((string) $redirect_to);
        if ($redirect_to === '') {
            return $fallback;
        }
        $home_host = wp_parse_url(home_url('/'), PHP_URL_HOST);
        $redirect_host = wp_parse_url($redirect_to, PHP_URL_HOST);
        if ($home_host && $redirect_host && !hash_equals((string) $home_host, (string) $redirect_host)) {
            return $fallback;
        }
        return $redirect_to;
    }


    private function get_default_team_redirect_url($post_id = 0) {
        $post_id = absint((int) $post_id);
        $profile_key = $this->resolve_profile_key_for_post($post_id);
        $account_url = $this->get_platform_page_url(
            'eep_page_my_events_url',
            $this->get_profile_route_slugs('my_items', $profile_key),
            $this->get_profile_route_default_url('my_items', $profile_key)
        );
        if (!is_string($account_url) || $account_url === '') {
            $account_url = home_url('/');
        }
        $args = [
            'eep_tab' => 'account',
            'account_subtab' => 'team-access',
        ];
        if ($post_id > 0) {
            $args['team_event'] = $post_id;
        }
        return eep_add_query_arg($args, $account_url) . '#account';
    }


    private function normalize_event_team_members_for_storage(array $rows) {
        $normalized = [];
        $now_utc = current_time('mysql', true);
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $member_id = sanitize_text_field((string) ($row['id'] ?? ''));
            if ($member_id === '') {
                $member_id = wp_generate_uuid4();
            }
            $row_user_id = absint((int) ($row['user_id'] ?? 0));
            $row_email = strtolower(sanitize_email((string) ($row['email'] ?? '')));
            if ($row_user_id > 0) {
                $row_user = get_userdata($row_user_id);
                if ($row_user instanceof WP_User) {
                    $resolved_email = strtolower(sanitize_email((string) $row_user->user_email));
                    if ($resolved_email !== '') {
                        $row_email = $resolved_email;
                    }
                }
            } elseif ($row_email !== '') {
                $matched_user = get_user_by('email', $row_email);
                if ($matched_user instanceof WP_User) {
                    $row_user_id = (int) $matched_user->ID;
                }
            }
            if ($row_user_id <= 0 && $row_email === '') {
                continue;
            }
            $row_role = $this->normalize_event_team_role($row['role'] ?? 'support');
            $row_status = $this->normalize_event_team_status($row['status'] ?? 'invited', 'invited');
            $scope_flags = $this->get_event_team_scope_flags((array) $row);
            if ($row_role === 'owner') {
                $scope_flags['website'] = '1';
                $scope_flags['webapp'] = '1';
            }
            $row_invited_by = absint((int) ($row['invited_by'] ?? 0));
            $row_invited_at = sanitize_text_field((string) ($row['invited_at'] ?? ''));
            if ($row_invited_at === '') {
                $row_invited_at = $now_utc;
            }
            $row_accepted_at = sanitize_text_field((string) ($row['accepted_at'] ?? ''));
            $row_token = sanitize_text_field((string) ($row['token'] ?? ''));
            if ($row_status === 'invited') {
                if ($row_token === '') {
                    $row_token = wp_generate_password(32, false, false);
                }
                $row_accepted_at = '';
            } elseif ($row_status === 'active') {
                if ($row_accepted_at === '') {
                    $row_accepted_at = $now_utc;
                }
                $row_token = '';
            } else {
                $row_accepted_at = '';
                $row_token = '';
            }

            $dedupe_key = $row_user_id > 0 ? ('u:' . $row_user_id) : ('e:' . $row_email);
            $clean_row = [
                'id' => $member_id,
                'email' => $row_email,
                'user_id' => $row_user_id,
                'role' => $row_role,
                'status' => $row_status,
                'scope_website' => $scope_flags['website'],
                'scope_webapp' => $scope_flags['webapp'],
                'invited_by' => $row_invited_by,
                'invited_at' => $row_invited_at,
                'accepted_at' => $row_accepted_at,
                'token' => $row_token,
            ];
            if (isset($normalized[$dedupe_key]) && is_array($normalized[$dedupe_key])) {
                $existing_row = $normalized[$dedupe_key];
                if ((string) ($existing_row['role'] ?? '') === 'owner' && $clean_row['role'] !== 'owner') {
                    $clean_row['role'] = 'owner';
                    $clean_row['scope_website'] = '1';
                    $clean_row['scope_webapp'] = '1';
                }
                if (!empty($existing_row['id'])) {
                    $clean_row['id'] = sanitize_text_field((string) $existing_row['id']);
                }
                if ($clean_row['invited_at'] === '' && !empty($existing_row['invited_at'])) {
                    $clean_row['invited_at'] = sanitize_text_field((string) $existing_row['invited_at']);
                }
                if ($clean_row['scope_website'] !== '1' && !empty($existing_row['scope_website'])) {
                    $clean_row['scope_website'] = $this->normalize_event_team_scope_flag($existing_row['scope_website'], '1');
                }
                if ($clean_row['scope_webapp'] !== '1' && !empty($existing_row['scope_webapp'])) {
                    $clean_row['scope_webapp'] = $this->normalize_event_team_scope_flag($existing_row['scope_webapp'], '1');
                }
            }
            $normalized[$dedupe_key] = $clean_row;
        }

        $rows_out = array_values($normalized);
        $role_order = ['owner' => 0, 'editor' => 1, 'app_moderator' => 2, 'support' => 3];
        usort($rows_out, static function ($a, $b) use ($role_order) {
            $a_role = sanitize_key((string) ($a['role'] ?? 'support'));
            $b_role = sanitize_key((string) ($b['role'] ?? 'support'));
            $a_rank = $role_order[$a_role] ?? 99;
            $b_rank = $role_order[$b_role] ?? 99;
            if ($a_rank !== $b_rank) {
                return $a_rank <=> $b_rank;
            }
            $a_email = strtolower(sanitize_email((string) ($a['email'] ?? '')));
            $b_email = strtolower(sanitize_email((string) ($b['email'] ?? '')));
            return strcmp($a_email, $b_email);
        });

        return $rows_out;
    }


    private function get_event_team_members($post_id) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return [];
        }
        $raw = get_post_meta($post_id, $this->get_event_team_meta_key(), true);
        if (is_string($raw) && $raw !== '') {
            $decoded = json_decode($raw, true);
            if (is_array($decoded)) {
                $raw = $decoded;
            }
        }
        if (!is_array($raw)) {
            return [];
        }
        return $this->normalize_event_team_members_for_storage($raw);
    }


    private function save_event_team_members($post_id, array $rows) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return;
        }
        $normalized = $this->normalize_event_team_members_for_storage($rows);
        if (empty($normalized)) {
            delete_post_meta($post_id, $this->get_event_team_meta_key());
            return;
        }
        update_post_meta($post_id, $this->get_event_team_meta_key(), wp_json_encode($normalized));
    }


    private function get_event_team_member_index(array $rows, $member_id = '', $email = '', $user_id = 0) {
        $member_id = sanitize_text_field((string) $member_id);
        $email = strtolower(sanitize_email((string) $email));
        $user_id = absint((int) $user_id);

        foreach ($rows as $index => $row) {
            if (!is_array($row)) {
                continue;
            }
            $row_member_id = sanitize_text_field((string) ($row['id'] ?? ''));
            $row_user_id = absint((int) ($row['user_id'] ?? 0));
            $row_email = strtolower(sanitize_email((string) ($row['email'] ?? '')));
            if ($member_id !== '' && $row_member_id !== '' && hash_equals($row_member_id, $member_id)) {
                return (int) $index;
            }
            if ($user_id > 0 && $row_user_id > 0 && $row_user_id === $user_id) {
                return (int) $index;
            }
            if ($email !== '' && $row_email !== '' && hash_equals($row_email, $email)) {
                return (int) $index;
            }
        }

        return -1;
    }


    private function ensure_event_owner_team_member($post_id) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return;
        }
        $owner_id = absint((int) get_post_field('post_author', $post_id));
        if ($owner_id <= 0) {
            return;
        }
        $owner_user = get_userdata($owner_id);
        $owner_email = $owner_user instanceof WP_User ? strtolower(sanitize_email((string) $owner_user->user_email)) : '';
        $rows = $this->get_event_team_members($post_id);
        $owner_index = $this->get_event_team_member_index($rows, '', $owner_email, $owner_id);
        $existing = $owner_index >= 0 && !empty($rows[$owner_index]) && is_array($rows[$owner_index]) ? $rows[$owner_index] : [];
        $rows[$owner_index >= 0 ? $owner_index : count($rows)] = [
            'id' => sanitize_text_field((string) ($existing['id'] ?? '')) !== '' ? sanitize_text_field((string) ($existing['id'] ?? '')) : wp_generate_uuid4(),
            'email' => $owner_email,
            'user_id' => $owner_id,
            'role' => 'owner',
            'status' => 'active',
            'scope_website' => '1',
            'scope_webapp' => '1',
            'invited_by' => absint((int) ($existing['invited_by'] ?? $owner_id)),
            'invited_at' => sanitize_text_field((string) ($existing['invited_at'] ?? '')) !== '' ? sanitize_text_field((string) ($existing['invited_at'] ?? '')) : current_time('mysql', true),
            'accepted_at' => sanitize_text_field((string) ($existing['accepted_at'] ?? '')) !== '' ? sanitize_text_field((string) ($existing['accepted_at'] ?? '')) : current_time('mysql', true),
            'token' => '',
        ];
        $this->save_event_team_members($post_id, $rows);
    }


    private function user_has_event_team_role($post_id, $user_id = 0, array $allowed_roles = ['owner', 'editor', 'app_moderator', 'support'], array $allowed_statuses = ['active'], $required_scope = '') {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return false;
        }
        $user_id = absint((int) $user_id);
        if ($user_id <= 0 && is_user_logged_in()) {
            $user_id = (int) get_current_user_id();
        }
        if ($user_id <= 0) {
            return false;
        }

        $normalized_roles = array_values(array_unique(array_filter(array_map(function ($role) {
            return $this->normalize_event_team_role($role);
        }, $allowed_roles))));
        $normalized_statuses = array_values(array_unique(array_filter(array_map(function ($status) {
            return $this->normalize_event_team_status($status, '');
        }, $allowed_statuses), static function ($status) {
            return $status !== '';
        })));
        if (empty($normalized_roles) || empty($normalized_statuses)) {
            return false;
        }
        $required_scope = sanitize_key((string) $required_scope);
        if (!in_array($required_scope, ['', 'website', 'webapp'], true)) {
            $required_scope = '';
        }

        $user = get_userdata($user_id);
        $user_email = $user instanceof WP_User ? strtolower(sanitize_email((string) $user->user_email)) : '';
        $rows = $this->get_event_team_members($post_id);
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $row_role = $this->normalize_event_team_role($row['role'] ?? 'support');
            $row_status = $this->normalize_event_team_status($row['status'] ?? 'invited', 'invited');
            if (!in_array($row_role, $normalized_roles, true) || !in_array($row_status, $normalized_statuses, true)) {
                continue;
            }
            if ($required_scope !== '' && !$this->event_team_member_has_scope((array) $row, $required_scope)) {
                continue;
            }
            $row_user_id = absint((int) ($row['user_id'] ?? 0));
            $row_email = strtolower(sanitize_email((string) ($row['email'] ?? '')));
            if ($row_user_id > 0 && $row_user_id === $user_id) {
                return true;
            }
            if ($row_email !== '' && $user_email !== '' && hash_equals($row_email, $user_email)) {
                return true;
            }
        }

        return false;
    }


    private function can_manage_event_team($post_id, $user_id = 0) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return false;
        }
        $user_id = absint((int) $user_id);
        if ($user_id <= 0 && is_user_logged_in()) {
            $user_id = (int) get_current_user_id();
        }
        if ($user_id <= 0) {
            return false;
        }
        if ($this->user_can_moderate_events($user_id)) {
            return true;
        }
        if ((int) get_post_field('post_author', $post_id) === $user_id) {
            return true;
        }
        // Team management is restricted to event admins (owner role) or platform moderators.
        return $this->user_has_event_team_role($post_id, $user_id, ['owner'], ['active']);
    }


    private function get_event_ids_for_team_user($user_id = 0) {
        $user_id = absint((int) $user_id);
        if ($user_id <= 0) {
            return [];
        }
        $user = get_userdata($user_id);
        $user_email = $user instanceof WP_User ? strtolower(sanitize_email((string) $user->user_email)) : '';
        $meta_key = $this->get_event_team_meta_key();
        $meta_query = [];
        $meta_query[] = [
            'key' => $meta_key,
            'value' => '"user_id":' . $user_id,
            'compare' => 'LIKE',
        ];
        if ($user_email !== '') {
            $email_fragment = '"email":"' . str_replace(['\\', '"'], ['\\\\', '\\"'], $user_email) . '"';
            $meta_query[] = [
                'key' => $meta_key,
                'value' => $email_fragment,
                'compare' => 'LIKE',
            ];
        }
        $query_args = [
            'post_type' => self::POST_TYPE,
            'post_status' => ['pending', 'publish', 'draft'],
            'posts_per_page' => -1,
            'fields' => 'ids',
            'no_found_rows' => true,
            'suppress_filters' => true,
        ];
        if (count($meta_query) === 1) {
            $query_args['meta_query'] = [$meta_query[0]];
        } else {
            $query_args['meta_query'] = array_merge(['relation' => 'OR'], $meta_query);
        }
        $candidate_ids = get_posts($query_args);
        if (!is_array($candidate_ids)) {
            return [];
        }
        $allowed = [];
        foreach ($candidate_ids as $candidate_id) {
            $candidate_id = absint((int) $candidate_id);
            if ($candidate_id <= 0) {
                continue;
            }
            if ($this->user_has_event_team_role($candidate_id, $user_id, ['owner', 'editor', 'app_moderator', 'support'], ['active'])) {
                $allowed[] = $candidate_id;
            }
        }
        return array_values(array_unique($allowed));
    }


    private function get_manageable_team_events_for_user($user_id = 0, $limit = 300) {
        $user_id = absint((int) $user_id);
        if ($user_id <= 0) {
            return [];
        }
        $limit = max(1, min(500, absint((int) $limit)));

        $candidate_ids = [];
        if ($this->user_can_moderate_events($user_id)) {
            $candidate_ids = get_posts([
                'post_type' => self::POST_TYPE,
                'post_status' => ['pending', 'publish', 'draft'],
                'posts_per_page' => $limit,
                'fields' => 'ids',
                'orderby' => 'date',
                'order' => 'DESC',
                'no_found_rows' => true,
                'suppress_filters' => true,
            ]);
        } else {
            $authored_ids = get_posts([
                'post_type' => self::POST_TYPE,
                'author' => $user_id,
                'post_status' => ['pending', 'publish', 'draft'],
                'posts_per_page' => $limit,
                'fields' => 'ids',
                'orderby' => 'date',
                'order' => 'DESC',
                'no_found_rows' => true,
                'suppress_filters' => true,
            ]);
            $team_ids = $this->get_event_ids_for_team_user($user_id);
            $candidate_ids = array_values(array_unique(array_merge(
                is_array($authored_ids) ? array_map('absint', $authored_ids) : [],
                is_array($team_ids) ? array_map('absint', $team_ids) : []
            )));
        }

        if (!is_array($candidate_ids) || empty($candidate_ids)) {
            return [];
        }

        $events = [];
        foreach ($candidate_ids as $candidate_id) {
            $candidate_id = absint((int) $candidate_id);
            if ($candidate_id <= 0 || get_post_type($candidate_id) !== self::POST_TYPE) {
                continue;
            }
            if (!$this->can_manage_event_team($candidate_id, $user_id)) {
                continue;
            }
            $this->ensure_event_owner_team_member($candidate_id);
            $events[] = [
                'id' => $candidate_id,
                'title' => sanitize_text_field((string) get_the_title($candidate_id)),
                'status' => sanitize_key((string) get_post_status($candidate_id)),
                'date_label' => sanitize_text_field((string) $this->format_event_date($candidate_id)),
            ];
        }

        return $events;
    }


    private function get_event_team_follower_candidates($post_id = 0) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return [];
        }

        $rows = $this->get_event_attendee_rows($post_id);
        if (empty($rows)) {
            return [];
        }

        $out = [];
        $seen = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $user_id = absint((int) ($row['user_id'] ?? 0));
            $email = strtolower(sanitize_email((string) ($row['email'] ?? '')));
            if ($user_id <= 0 || $email === '') {
                continue;
            }
            $key = $user_id > 0 ? ('u:' . $user_id) : ('e:' . $email);
            if (isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;
            $name = sanitize_text_field((string) ($row['name'] ?? ''));
            if ($name === '') {
                $name = $email;
            }
            $out[] = [
                'user_id' => $user_id,
                'email' => $email,
                'name' => $name,
            ];
        }

        return $out;
    }


    private function get_event_team_members_for_template($post_id) {
        $rows = $this->get_event_team_members($post_id);
        if (empty($rows)) {
            return [];
        }
        $owner_id = (int) get_post_field('post_author', $post_id);
        $user_ids = [];
        foreach ($rows as $row) {
            $row_user_id = absint((int) ($row['user_id'] ?? 0));
            if ($row_user_id > 0) {
                $user_ids[] = $row_user_id;
            }
        }
        $user_ids = array_values(array_unique($user_ids));
        $user_map = [];
        if (!empty($user_ids)) {
            $users = get_users([
                'include' => $user_ids,
                'fields' => ['ID', 'display_name', 'user_login'],
            ]);
            foreach ((array) $users as $user) {
                if (!$user instanceof WP_User) {
                    continue;
                }
                $user_map[(int) $user->ID] = sanitize_text_field((string) ($user->display_name ?: $user->user_login));
            }
        }

        $members = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $member_id = sanitize_text_field((string) ($row['id'] ?? ''));
            $row_email = strtolower(sanitize_email((string) ($row['email'] ?? '')));
            $row_user_id = absint((int) ($row['user_id'] ?? 0));
            if ($member_id === '' || ($row_email === '' && $row_user_id <= 0)) {
                continue;
            }
            $display_name = sanitize_text_field((string) ($user_map[$row_user_id] ?? ''));
            if ($display_name === '' && $row_email !== '') {
                $display_name = sanitize_text_field((string) strstr($row_email, '@', true));
            }
            if ($display_name === '') {
                $display_name = __('Member', 'nexdirectory');
            }
            $row_role = $this->normalize_event_team_role($row['role'] ?? 'support');
            $row_status = $this->normalize_event_team_status($row['status'] ?? 'invited', 'invited');
            $scope_flags = $this->get_event_team_scope_flags((array) $row);
            $members[] = [
                'id' => $member_id,
                'email' => $row_email,
                'user_id' => $row_user_id,
                'display_name' => $display_name,
                'role' => $row_role,
                'role_label' => $this->get_event_team_role_label($row_role),
                'status' => $row_status,
                'status_label' => $this->get_event_team_status_label($row_status),
                'scope_website' => (string) ($scope_flags['website'] ?? '1'),
                'scope_webapp' => (string) ($scope_flags['webapp'] ?? '1'),
                'scope_label' => $this->get_event_team_scope_label((array) $row),
                'invited_at' => sanitize_text_field((string) ($row['invited_at'] ?? '')),
                'accepted_at' => sanitize_text_field((string) ($row['accepted_at'] ?? '')),
                'is_primary_owner' => $row_role === 'owner' && $row_user_id > 0 && $owner_id > 0 && $row_user_id === $owner_id,
            ];
        }

        return $members;
    }


    private function send_event_team_invite_email($post_id, array $member) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return false;
        }
        $email = strtolower(sanitize_email((string) ($member['email'] ?? '')));
        $token = sanitize_text_field((string) ($member['token'] ?? ''));
        if (!is_email($email) || $token === '') {
            return false;
        }
        $role = $this->normalize_event_team_role($member['role'] ?? 'support');
        $role_label = $this->get_event_team_role_label($role);
        $scope_label = $this->get_event_team_scope_label($member);
        $event_title = sanitize_text_field((string) get_the_title($post_id));
        $event_url = (string) get_permalink($post_id);
        $site_name = sanitize_text_field((string) $this->get_white_label_brand_name());
        $accept_url = eep_add_query_arg([
            'action' => 'eep_accept_event_team_invite',
            'event_id' => $post_id,
            'token' => $token,
        ], admin_url('admin-post.php'));

        $subject = sprintf(__('Invitation to manage "%1$s" on %2$s', 'nexdirectory'), $event_title, $site_name);
        $custom_body_html = '<p>' . esc_html__('Hello,', 'nexdirectory') . '</p>'
            . '<p>' . sprintf(
                esc_html__('You were invited to join the team for "%1$s" as %2$s.', 'nexdirectory'),
                esc_html($event_title),
                esc_html($role_label)
            ) . '</p>'
            . '<p>' . sprintf(
                esc_html__('Access scope: %s.', 'nexdirectory'),
                esc_html($scope_label)
            ) . '</p>'
            . '<p><a href="' . esc_url($accept_url) . '">' . esc_html__('Accept team invitation', 'nexdirectory') . '</a></p>'
            . '<p>' . esc_html__('After accepting, you can access this listing in your workspace.', 'nexdirectory') . '</p>'
            . '<p><a href="' . esc_url($event_url) . '">' . esc_html__('View listing', 'nexdirectory') . '</a></p>';
        $fallback_text = sprintf(
            "You were invited to join the team for \"%s\" as %s.\nAccess scope: %s.\n\nAccept invitation: %s\nView listing: %s",
            $event_title,
            $role_label,
            $scope_label,
            $accept_url,
            $event_url
        );

        return (bool) $this->send_platform_email($email, $subject, '', [
            'post_id' => $post_id,
            'event_title' => $event_title,
            'event_url' => $event_url,
            'custom_body_html' => $custom_body_html,
            'fallback_text' => $fallback_text,
        ]);
    }


    public function handle_invite_event_team_member() {
        if (!is_user_logged_in()) {
            wp_die(__('You must be logged in.', 'nexdirectory'));
        }

        $current_user_id = (int) get_current_user_id();
        $nonce_event_id = absint((int) wp_unslash($_POST['event_id'] ?? 0));
        $event_ids = [];
        if (!empty($_POST['team_event_ids']) && is_array($_POST['team_event_ids'])) {
            foreach ((array) wp_unslash($_POST['team_event_ids']) as $candidate_id) {
                $candidate_id = absint((int) $candidate_id);
                if ($candidate_id > 0) {
                    $event_ids[] = $candidate_id;
                }
            }
        }
        if ($nonce_event_id > 0 && empty($event_ids)) {
            $event_ids[] = $nonce_event_id;
        }
        $event_ids = array_values(array_unique(array_filter(array_map('absint', $event_ids))));
        if ($nonce_event_id <= 0 && !empty($event_ids)) {
            $nonce_event_id = (int) $event_ids[0];
        }

        $fallback_redirect = $this->get_default_team_redirect_url($nonce_event_id);
        $redirect_to = $this->get_safe_redirect_url(wp_unslash($_POST['redirect_to'] ?? ''), $fallback_redirect);
        if ($nonce_event_id <= 0 || get_post_type($nonce_event_id) !== self::POST_TYPE) {
            $this->redirect_with_message($redirect_to, __('Invalid listing selected.', 'nexdirectory'), 'error');
        }
        if (!$this->can_manage_event_team($nonce_event_id, $current_user_id)) {
            $this->redirect_with_message($redirect_to, __('You do not have permission to manage this listing team.', 'nexdirectory'), 'error');
        }
        if (
            !isset($_POST['eep_event_team_invite_nonce'])
            || !wp_verify_nonce(
                eep_sanitize_text(wp_unslash($_POST['eep_event_team_invite_nonce'])),
                'eep_event_team_invite_' . $nonce_event_id
            )
        ) {
            $this->redirect_with_message($redirect_to, __('Invalid request.', 'nexdirectory'), 'error');
        }

        if (empty($event_ids)) {
            $event_ids = [$nonce_event_id];
        }

        $target_event_ids = [];
        foreach ($event_ids as $event_id) {
            $event_id = absint((int) $event_id);
            if ($event_id <= 0 || get_post_type($event_id) !== self::POST_TYPE) {
                continue;
            }
            if (!$this->can_manage_event_team($event_id, $current_user_id)) {
                continue;
            }
            $target_event_ids[] = $event_id;
        }
        $target_event_ids = array_values(array_unique($target_event_ids));
        if (empty($target_event_ids)) {
            $this->redirect_with_message($redirect_to, __('Select at least one event you can manage.', 'nexdirectory'), 'error');
        }

        $email = strtolower(sanitize_email((string) wp_unslash($_POST['team_email'] ?? '')));
        if (!is_email($email)) {
            $follower_user_id = absint((int) wp_unslash($_POST['team_follower_user_id'] ?? 0));
            if ($follower_user_id > 0) {
                $follower_user = get_user_by('id', $follower_user_id);
                if ($follower_user instanceof WP_User) {
                    $email = strtolower(sanitize_email((string) $follower_user->user_email));
                }
            }
        }
        if (!is_email($email)) {
            $email = strtolower(sanitize_email((string) wp_unslash($_POST['team_follower_email'] ?? '')));
        }
        if (!is_email($email)) {
            $this->redirect_with_message($redirect_to, __('Please enter a valid team member email address.', 'nexdirectory'), 'error');
        }

        $role = $this->normalize_event_team_role(wp_unslash($_POST['team_role'] ?? 'editor'));
        $status = $this->normalize_event_team_status(wp_unslash($_POST['team_status'] ?? 'invited'), 'invited');
        $member_user = get_user_by('email', $email);
        $member_user_id = $member_user instanceof WP_User ? (int) $member_user->ID : 0;

        $scope_website = $this->normalize_event_team_scope_flag(wp_unslash($_POST['team_scope_website'] ?? '0'), '0');
        $scope_webapp = $this->normalize_event_team_scope_flag(wp_unslash($_POST['team_scope_webapp'] ?? '1'), '1');
        if ($role === 'owner') {
            $scope_website = '1';
            $scope_webapp = '1';
        } elseif ($role === 'editor') {
            $scope_website = '1';
        } elseif ($role === 'app_moderator') {
            $scope_webapp = '1';
        }
        if ($scope_website !== '1' && $scope_webapp !== '1') {
            $this->redirect_with_message($redirect_to, __('Choose at least one access scope: Website or Web App.', 'nexdirectory'), 'error');
        }

        $now_utc = current_time('mysql', true);
        $added_count = 0;
        $updated_count = 0;
        $invite_success_count = 0;
        $invite_failure_count = 0;

        foreach ($target_event_ids as $target_event_id) {
            $rows = $this->get_event_team_members($target_event_id);
            $existing_index = $this->get_event_team_member_index($rows, '', $email, $member_user_id);
            $existing = $existing_index >= 0 && !empty($rows[$existing_index]) && is_array($rows[$existing_index]) ? $rows[$existing_index] : [];

            $member_row = [
                'id' => sanitize_text_field((string) ($existing['id'] ?? '')) !== '' ? sanitize_text_field((string) ($existing['id'] ?? '')) : wp_generate_uuid4(),
                'email' => $email,
                'user_id' => $member_user_id,
                'role' => $role,
                'status' => $status,
                'scope_website' => $scope_website,
                'scope_webapp' => $scope_webapp,
                'invited_by' => $current_user_id,
                'invited_at' => sanitize_text_field((string) ($existing['invited_at'] ?? '')) !== '' ? sanitize_text_field((string) ($existing['invited_at'] ?? '')) : $now_utc,
                'accepted_at' => $status === 'active'
                    ? (sanitize_text_field((string) ($existing['accepted_at'] ?? '')) !== '' ? sanitize_text_field((string) ($existing['accepted_at'] ?? '')) : $now_utc)
                    : '',
                'token' => $status === 'invited' ? wp_generate_password(32, false, false) : '',
            ];
            $rows[$existing_index >= 0 ? $existing_index : count($rows)] = $member_row;
            $this->save_event_team_members($target_event_id, $rows);
            $this->ensure_event_owner_team_member($target_event_id);

            if ($existing_index >= 0) {
                $updated_count++;
            } else {
                $added_count++;
            }

            if ($status === 'invited') {
                $invite_sent = $this->send_event_team_invite_email($target_event_id, $member_row);
                if ($invite_sent) {
                    $invite_success_count++;
                } else {
                    $invite_failure_count++;
                }
            }
        }

        $applied_count = $added_count + $updated_count;
        $message = $updated_count > 0 && $added_count <= 0
            ? __('Team member updated successfully.', 'nexdirectory')
            : __('Team member added successfully.', 'nexdirectory');
        if ($applied_count > 1) {
            $message .= ' ' . sprintf(__('Applied to %d events.', 'nexdirectory'), $applied_count);
        }
        if ($status === 'invited') {
            if ($invite_failure_count > 0) {
                $message .= ' ' . sprintf(__('Email sent for %1$d event(s), but failed for %2$d.', 'nexdirectory'), max(0, $invite_success_count), $invite_failure_count);
            } else {
                $message .= ' ' . __('Invitation email sent automatically.', 'nexdirectory');
            }
        }
        $message_type = $invite_failure_count > 0 ? 'error' : 'success';
        $this->redirect_with_message($redirect_to, $message, $message_type);
    }


    public function handle_update_event_team_member() {
        if (!is_user_logged_in()) {
            wp_die(__('You must be logged in.', 'nexdirectory'));
        }

        $post_id = absint((int) wp_unslash($_POST['event_id'] ?? 0));
        $fallback_redirect = $this->get_default_team_redirect_url($post_id);
        $redirect_to = $this->get_safe_redirect_url(wp_unslash($_POST['redirect_to'] ?? ''), $fallback_redirect);
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            $this->redirect_with_message($redirect_to, __('Invalid listing selected.', 'nexdirectory'), 'error');
        }
        if (!$this->can_manage_event_team($post_id, get_current_user_id())) {
            $this->redirect_with_message($redirect_to, __('You do not have permission to manage this listing team.', 'nexdirectory'), 'error');
        }
        if (
            !isset($_POST['eep_event_team_manage_nonce'])
            || !wp_verify_nonce(
                eep_sanitize_text(wp_unslash($_POST['eep_event_team_manage_nonce'])),
                'eep_event_team_manage_' . $post_id
            )
        ) {
            $this->redirect_with_message($redirect_to, __('Invalid request.', 'nexdirectory'), 'error');
        }

        $member_id = sanitize_text_field((string) wp_unslash($_POST['member_id'] ?? ''));
        if ($member_id === '') {
            $this->redirect_with_message($redirect_to, __('Team member is missing.', 'nexdirectory'), 'error');
        }

        $rows = $this->get_event_team_members($post_id);
        $index = $this->get_event_team_member_index($rows, $member_id);
        if ($index < 0 || empty($rows[$index]) || !is_array($rows[$index])) {
            $this->redirect_with_message($redirect_to, __('Team member was not found.', 'nexdirectory'), 'error');
        }
        $current_row = $rows[$index];
        $current_role = $this->normalize_event_team_role($current_row['role'] ?? 'support');
        $current_status = $this->normalize_event_team_status($current_row['status'] ?? 'invited', 'invited');
        $current_scope_flags = $this->get_event_team_scope_flags((array) $current_row);
        $new_role = $this->normalize_event_team_role(wp_unslash($_POST['team_role'] ?? $current_role));
        $new_status = $this->normalize_event_team_status(wp_unslash($_POST['team_status'] ?? $current_status), $current_status);
        $new_scope_website = $this->normalize_event_team_scope_flag(wp_unslash($_POST['team_scope_website'] ?? $current_scope_flags['website']), $current_scope_flags['website']);
        $new_scope_webapp = $this->normalize_event_team_scope_flag(wp_unslash($_POST['team_scope_webapp'] ?? $current_scope_flags['webapp']), $current_scope_flags['webapp']);

        $event_owner_id = (int) get_post_field('post_author', $post_id);
        $member_user_id = absint((int) ($current_row['user_id'] ?? 0));
        if ($member_user_id > 0 && $event_owner_id > 0 && $member_user_id === $event_owner_id) {
            $new_role = 'owner';
            $new_status = 'active';
            $new_scope_website = '1';
            $new_scope_webapp = '1';
        } elseif ($current_role === 'owner' && ($new_role !== 'owner' || $new_status !== 'active')) {
            $other_active_owner = false;
            foreach ($rows as $row_index => $row) {
                if ((int) $row_index === $index || !is_array($row)) {
                    continue;
                }
                $role_value = $this->normalize_event_team_role($row['role'] ?? 'support');
                $status_value = $this->normalize_event_team_status($row['status'] ?? 'invited', 'invited');
                if ($role_value === 'owner' && $status_value === 'active') {
                    $other_active_owner = true;
                    break;
                }
            }
            if (!$other_active_owner) {
                $this->redirect_with_message($redirect_to, __('At least one active owner is required.', 'nexdirectory'), 'error');
            }
        }

        if ($new_role === 'owner') {
            $new_scope_website = '1';
            $new_scope_webapp = '1';
        } elseif ($new_role === 'editor') {
            $new_scope_website = '1';
        } elseif ($new_role === 'app_moderator') {
            $new_scope_webapp = '1';
        }
        if ($new_scope_website !== '1' && $new_scope_webapp !== '1') {
            $this->redirect_with_message($redirect_to, __('Choose at least one access scope: Website or Web App.', 'nexdirectory'), 'error');
        }

        $now_utc = current_time('mysql', true);
        $updated_row = $current_row;
        $updated_row['role'] = $new_role;
        $updated_row['status'] = $new_status;
        $updated_row['scope_website'] = $new_scope_website;
        $updated_row['scope_webapp'] = $new_scope_webapp;
        if ($new_status === 'active') {
            $updated_row['accepted_at'] = sanitize_text_field((string) ($current_row['accepted_at'] ?? '')) !== ''
                ? sanitize_text_field((string) ($current_row['accepted_at'] ?? ''))
                : $now_utc;
            $updated_row['token'] = '';
        } elseif ($new_status === 'invited') {
            $updated_row['accepted_at'] = '';
            $updated_row['token'] = sanitize_text_field((string) ($current_row['token'] ?? ''));
            if ((string) $updated_row['token'] === '') {
                $updated_row['token'] = wp_generate_password(32, false, false);
            }
        } else {
            $updated_row['accepted_at'] = '';
            $updated_row['token'] = '';
        }

        $rows[$index] = $updated_row;
        $this->save_event_team_members($post_id, $rows);
        $this->ensure_event_owner_team_member($post_id);

        $invite_sent = true;
        if ($new_status === 'invited') {
            $invite_sent = $this->send_event_team_invite_email($post_id, (array) $updated_row);
        }
        $message = __('Team member settings updated.', 'nexdirectory');
        if ($new_status === 'invited') {
            $message .= $invite_sent
                ? ' ' . __('Invitation email sent.', 'nexdirectory')
                : ' ' . __('Invite status saved, but email could not be sent.', 'nexdirectory');
        }
        $this->redirect_with_message($redirect_to, $message, $invite_sent ? 'success' : 'error');
    }


    public function handle_remove_event_team_member() {
        if (!is_user_logged_in()) {
            wp_die(__('You must be logged in.', 'nexdirectory'));
        }

        $post_id = absint((int) wp_unslash($_POST['event_id'] ?? 0));
        $fallback_redirect = $this->get_default_team_redirect_url($post_id);
        $redirect_to = $this->get_safe_redirect_url(wp_unslash($_POST['redirect_to'] ?? ''), $fallback_redirect);
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            $this->redirect_with_message($redirect_to, __('Invalid listing selected.', 'nexdirectory'), 'error');
        }
        if (!$this->can_manage_event_team($post_id, get_current_user_id())) {
            $this->redirect_with_message($redirect_to, __('You do not have permission to manage this listing team.', 'nexdirectory'), 'error');
        }
        if (
            !isset($_POST['eep_event_team_manage_nonce'])
            || !wp_verify_nonce(
                eep_sanitize_text(wp_unslash($_POST['eep_event_team_manage_nonce'])),
                'eep_event_team_manage_' . $post_id
            )
        ) {
            $this->redirect_with_message($redirect_to, __('Invalid request.', 'nexdirectory'), 'error');
        }

        $member_id = sanitize_text_field((string) wp_unslash($_POST['member_id'] ?? ''));
        if ($member_id === '') {
            $this->redirect_with_message($redirect_to, __('Team member is missing.', 'nexdirectory'), 'error');
        }

        $rows = $this->get_event_team_members($post_id);
        $index = $this->get_event_team_member_index($rows, $member_id);
        if ($index < 0 || empty($rows[$index]) || !is_array($rows[$index])) {
            $this->redirect_with_message($redirect_to, __('Team member was not found.', 'nexdirectory'), 'error');
        }

        $row = $rows[$index];
        $event_owner_id = (int) get_post_field('post_author', $post_id);
        $row_user_id = absint((int) ($row['user_id'] ?? 0));
        $row_role = $this->normalize_event_team_role($row['role'] ?? 'support');
        if ($row_role === 'owner' && $row_user_id > 0 && $event_owner_id > 0 && $row_user_id === $event_owner_id) {
            $this->redirect_with_message($redirect_to, __('Primary owner cannot be removed from the event team.', 'nexdirectory'), 'error');
        }
        if ($row_role === 'owner') {
            $other_owner_exists = false;
            foreach ($rows as $row_index => $candidate_row) {
                if ((int) $row_index === $index || !is_array($candidate_row)) {
                    continue;
                }
                $candidate_role = $this->normalize_event_team_role($candidate_row['role'] ?? 'support');
                $candidate_status = $this->normalize_event_team_status($candidate_row['status'] ?? 'invited', 'invited');
                if ($candidate_role === 'owner' && $candidate_status === 'active') {
                    $other_owner_exists = true;
                    break;
                }
            }
            if (!$other_owner_exists) {
                $this->redirect_with_message($redirect_to, __('At least one active owner is required.', 'nexdirectory'), 'error');
            }
        }

        unset($rows[$index]);
        $this->save_event_team_members($post_id, array_values($rows));
        $this->ensure_event_owner_team_member($post_id);
        $this->redirect_with_message($redirect_to, __('Team member removed.', 'nexdirectory'));
    }


    public function handle_accept_event_team_invite() {
        $request_event_id = absint((int) wp_unslash($_REQUEST['event_id'] ?? 0));
        $request_token = sanitize_text_field((string) wp_unslash($_REQUEST['token'] ?? ''));
        $redirect_to = $this->get_default_team_redirect_url($request_event_id);
        if ($request_event_id <= 0 || get_post_type($request_event_id) !== self::POST_TYPE || $request_token === '') {
            $this->redirect_with_message($redirect_to, __('Invite link is invalid or expired.', 'nexdirectory'), 'error');
        }

        $rows = $this->get_event_team_members($request_event_id);
        $match_index = -1;
        foreach ($rows as $row_index => $row) {
            if (!is_array($row)) {
                continue;
            }
            $row_token = sanitize_text_field((string) ($row['token'] ?? ''));
            $row_status = $this->normalize_event_team_status($row['status'] ?? 'invited', 'invited');
            if ($row_status !== 'invited' || $row_token === '') {
                continue;
            }
            if (hash_equals($row_token, $request_token)) {
                $match_index = (int) $row_index;
                break;
            }
        }
        if ($match_index < 0 || empty($rows[$match_index]) || !is_array($rows[$match_index])) {
            $this->redirect_with_message($redirect_to, __('Invite link is invalid or has already been used.', 'nexdirectory'), 'error');
        }
        if (!is_user_logged_in()) {
            $accept_url = eep_add_query_arg([
                'action' => 'eep_accept_event_team_invite',
                'event_id' => $request_event_id,
                'token' => $request_token,
            ], admin_url('admin-post.php'));
            wp_safe_redirect(wp_login_url($accept_url));
            exit;
        }

        $current_user = wp_get_current_user();
        $current_user_id = (int) get_current_user_id();
        $current_user_email = strtolower(sanitize_email((string) ($current_user->user_email ?? '')));
        $invited_email = strtolower(sanitize_email((string) ($rows[$match_index]['email'] ?? '')));
        if (
            $invited_email !== ''
            && $current_user_email !== ''
            && !hash_equals($invited_email, $current_user_email)
            && !$this->user_can_moderate_events($current_user_id)
        ) {
            $this->redirect_with_message($redirect_to, __('This invite was issued for a different email account.', 'nexdirectory'), 'error');
        }

        $rows[$match_index]['user_id'] = $current_user_id;
        $rows[$match_index]['email'] = $current_user_email;
        $rows[$match_index]['status'] = 'active';
        $rows[$match_index]['accepted_at'] = current_time('mysql', true);
        $rows[$match_index]['token'] = '';
        $this->save_event_team_members($request_event_id, $rows);
        $this->ensure_event_owner_team_member($request_event_id);

        $this->redirect_with_message($redirect_to, __('Team invitation accepted. You can now manage this listing.', 'nexdirectory'));
    }


    private function get_event_optional_sections_missing($post_id, $profile_key = '') {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return [];
        }
        $resolved_profile_key = $this->resolve_profile_key_for_post($post_id, $profile_key);
        $ui_labels = $this->get_site_profile_ui_labels($resolved_profile_key);
        $profile_key = sanitize_key((string) ($ui_labels['profile_key'] ?? 'events'));
        $is_events_profile = $profile_key === 'events';
        $is_job_profile = $profile_key === 'job_board';
        $is_marketplace_profile = $profile_key === 'marketplace';

        $missing = [];
        $content_raw = trim((string) eep_strip_all_tags((string) get_post_field('post_content', $post_id)));
        if ($content_raw === '') {
            $missing[] = __('Overview description', 'nexdirectory');
        }

        $venue = trim((string) get_post_meta($post_id, 'eep_venue', true));
        $address = trim((string) get_post_meta($post_id, 'eep_address', true));
        $city = trim((string) get_post_meta($post_id, 'eep_city', true));
        $country = trim((string) get_post_meta($post_id, 'eep_country', true));
        $lat = get_post_meta($post_id, 'eep_lat', true);
        $lng = get_post_meta($post_id, 'eep_lng', true);
        $has_location = ($venue !== '' || $address !== '' || $city !== '' || $country !== '' || (is_numeric($lat) && is_numeric($lng)));
        $website = trim((string) get_post_meta($post_id, 'eep_external_url', true));

        $contact_email = sanitize_email((string) get_post_meta($post_id, 'eep_contact_email', true));
        $contact_phone = trim((string) get_post_meta($post_id, 'eep_contact_phone', true));
        $has_contact_details = $contact_email !== '' || $contact_phone !== '';

        if ($is_events_profile) {
            if (!$has_location) {
                $missing[] = __('Location details', 'nexdirectory');
            }
            $gallery_urls = $this->get_event_gallery_image_urls($post_id, 'thumbnail');
            $cover_url = esc_url_raw((string) get_post_meta($post_id, 'eep_cover_image_url', true));
            if ($cover_url !== '') {
                $normalized_cover = untrailingslashit($cover_url);
                $gallery_urls = array_values(array_filter($gallery_urls, static function ($url) use ($normalized_cover) {
                    return untrailingslashit((string) $url) !== $normalized_cover;
                }));
            }
            if (empty($gallery_urls)) {
                $missing[] = __('Gallery images', 'nexdirectory');
            }
            $tag_ids = wp_get_post_terms($post_id, self::TAX_TAG, ['fields' => 'ids']);
            if (is_wp_error($tag_ids) || empty($tag_ids)) {
                $missing[] = __('Tags', 'nexdirectory');
            }
            $sessions = $this->parse_sessions((string) get_post_meta($post_id, 'eep_sessions', true));
            if (empty($sessions)) {
                $missing[] = __('A Day Of Action itinerary', 'nexdirectory');
            }
            if ($website === '') {
                $missing[] = __('Event website URL', 'nexdirectory');
            }
            $end_value = trim((string) get_post_meta($post_id, 'eep_end', true));
            if ($end_value === '') {
                $missing[] = __('End date', 'nexdirectory');
            }
            if (!$has_contact_details) {
                $missing[] = __('Contact details', 'nexdirectory');
            }
        } elseif ($is_job_profile) {
            $salary = trim((string) get_post_meta($post_id, 'eep_job_salary', true));
            if ($salary === '') {
                $missing[] = __('Salary range', 'nexdirectory');
            }
            $work_mode = trim((string) get_post_meta($post_id, 'eep_job_work_mode', true));
            if ($work_mode === '') {
                $missing[] = __('Work location type', 'nexdirectory');
            }
            $job_type = trim((string) get_post_meta($post_id, 'eep_job_type', true));
            if ($job_type === '') {
                $missing[] = __('Employment type', 'nexdirectory');
            }
            $apply_url = trim((string) get_post_meta($post_id, 'eep_job_apply_url', true));
            $apply_email = sanitize_email((string) get_post_meta($post_id, 'eep_job_application_email', true));
            if ($apply_url === '' && $apply_email === '') {
                $missing[] = __('Application method', 'nexdirectory');
            }
            $closing_date = trim((string) get_post_meta($post_id, 'eep_job_closing_date', true));
            if ($closing_date === '') {
                $missing[] = __('Closing date', 'nexdirectory');
            }
            $company_name = trim((string) get_post_meta($post_id, 'eep_company_name', true));
            if ($company_name === '') {
                $missing[] = __('Company name', 'nexdirectory');
            }
            if (!$has_contact_details && $apply_email === '') {
                $missing[] = __('Recruiter contact details', 'nexdirectory');
            }
        } elseif ($is_marketplace_profile) {
            $price_value = trim((string) get_post_meta($post_id, 'eep_price', true));
            if ($price_value === '') {
                $missing[] = __('Asking price', 'nexdirectory');
            }
            $price_type = sanitize_key((string) get_post_meta($post_id, 'eep_market_price_type', true));
            if (!in_array($price_type, ['fixed', 'negotiable', 'auction', 'free'], true)) {
                $missing[] = __('Price type', 'nexdirectory');
            }
            $condition = trim((string) get_post_meta($post_id, 'eep_market_condition', true));
            if ($condition === '') {
                $missing[] = __('Condition', 'nexdirectory');
            }
            $delivery_methods = preg_split('/[\s,]+/', (string) get_post_meta($post_id, 'eep_market_delivery_methods', true));
            if (!is_array($delivery_methods) || empty(array_filter(array_map('sanitize_key', $delivery_methods)))) {
                $missing[] = __('Delivery methods', 'nexdirectory');
            }
            if (!$has_location) {
                $missing[] = __('Location details', 'nexdirectory');
            }
            if (!$has_contact_details && $website === '') {
                $missing[] = __('Contact method', 'nexdirectory');
            }
        } else {
            if (!$has_location) {
                $missing[] = __('Location details', 'nexdirectory');
            }
            $tag_ids = wp_get_post_terms($post_id, self::TAX_TAG, ['fields' => 'ids']);
            if (is_wp_error($tag_ids) || empty($tag_ids)) {
                $missing[] = __('Tags', 'nexdirectory');
            }
            if ($website === '') {
                $missing[] = __('Website URL', 'nexdirectory');
            }
            if (!$has_contact_details) {
                $missing[] = __('Contact details', 'nexdirectory');
            }
        }

        return array_values(array_unique(array_filter(array_map('eep_sanitize_text', $missing))));
    }


    private function get_event_optional_sections_hint_text(array $missing, $max_items = 4) {
        $clean = array_values(array_filter(array_map(static function ($item) {
            return sanitize_text_field((string) $item);
        }, $missing), static function ($item) {
            return trim((string) $item) !== '';
        }));
        if (empty($clean)) {
            return '';
        }
        $max_items = max(1, (int) $max_items);
        $visible = array_slice($clean, 0, $max_items);
        $remaining = max(0, count($clean) - count($visible));
        $suffix = $remaining > 0 ? sprintf(__(' (+%d more)', 'nexdirectory'), $remaining) : '';
        return sprintf(
            __('Tip: add %1$s to complete this listing%2$s.', 'nexdirectory'),
            implode(', ', $visible),
            $suffix
        );
    }


    private function resolve_submit_prefill_source_post_id($post_id) {
        $post_id = absint((int) $post_id);
        return $post_id > 0 ? $post_id : 0;
    }


    private function recover_existing_event_title_value($post_id, $fallback = '') {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return sanitize_text_field((string) $fallback);
        }

        $title = trim(sanitize_text_field((string) $fallback));
        if ($title === '') {
            $title = trim(sanitize_text_field((string) get_the_title($post_id)));
        }
        if ($title === '') {
            foreach (['eep_meta_title', 'eep_import_title', 'eep_title', 'title'] as $meta_key) {
                $meta_title = trim(sanitize_text_field((string) get_post_meta($post_id, $meta_key, true)));
                if ($meta_title !== '') {
                    $title = $meta_title;
                    break;
                }
            }
        }
        if ($title === '') {
            $post_name = sanitize_title((string) get_post_field('post_name', $post_id));
            if ($post_name !== '') {
                $title = trim(sanitize_text_field(str_replace(['-', '_'], ' ', $post_name)));
            }
        }
        if ($title === '') {
            $title = sprintf(__('Untitled Listing #%d', 'nexdirectory'), $post_id);
        }

        return $title;
    }


    private function recover_existing_event_datetime_value($post_id, $meta_key = 'eep_start') {
        $post_id = absint((int) $post_id);
        $meta_key = sanitize_key((string) $meta_key);
        if ($post_id <= 0 || !in_array($meta_key, ['eep_start', 'eep_end'], true)) {
            return '';
        }
        $candidate_keys = $meta_key === 'eep_start'
            ? ['eep_start', 'start', 'event_start', 'eep_start_raw', 'eep_start_date']
            : ['eep_end', 'end', 'event_end', 'eep_end_raw', 'eep_end_date'];
        foreach ($candidate_keys as $candidate_key) {
            $normalized = $this->normalize_prefill_datetime_meta_value((string) get_post_meta($post_id, $candidate_key, true));
            if ($normalized !== '') {
                return $normalized;
            }
        }
        return '';
    }


    private function normalize_prefill_datetime_meta_value($value) {
        $raw = trim(eep_scalar_to_string($value));
        if ($raw === '') {
            return '';
        }

        if (is_numeric($raw)) {
            $timestamp = (int) $raw;
            if ($timestamp > 9999999999) {
                $timestamp = (int) floor($timestamp / 1000);
            }
            if ($timestamp > 0) {
                return gmdate('Y-m-d H:i:s', $timestamp);
            }
        }

        $normalized = trim((string) str_replace('T', ' ', $raw));
        if ($normalized === '') {
            return '';
        }
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $normalized) === 1) {
            return $normalized . ' 00:00:00';
        }
        if (preg_match('/^\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}(?::\d{2})?$/', $normalized) === 1) {
            $time_part = trim((string) substr($normalized, 10));
            if (preg_match('/^\d{1,2}:\d{2}$/', $time_part) === 1) {
                $time_part .= ':00';
            }
            return trim(substr($normalized, 0, 10) . ' ' . $time_part);
        }

        $timestamp = strtotime($normalized);
        if ($timestamp !== false && $timestamp > 0) {
            $has_time = preg_match('/\b\d{1,2}:\d{2}(:\d{2})?\b/', $normalized) === 1;
            return $has_time ? wp_date('Y-m-d H:i:s', $timestamp) : wp_date('Y-m-d 00:00:00', $timestamp);
        }

        return '';
    }


    private function parse_ticket_price_amount_value($raw_value) {
        $raw_value = trim((string) $raw_value);
        if ($raw_value === '') {
            return 0.0;
        }
        $normalized = str_replace(',', '.', $raw_value);
        if (is_numeric($normalized)) {
            return max(0.0, (float) $normalized);
        }
        if (preg_match('/\d+(?:\.\d{1,2})?/', $normalized, $match)) {
            return max(0.0, (float) ($match[0] ?? 0));
        }
        return 0.0;
    }


    private function format_ticket_price_amount_label($amount) {
        $amount = max(0.0, (float) $amount);
        if ($amount <= 0) {
            return __('Free', 'nexdirectory');
        }
        $currency = method_exists($this, 'get_ticket_currency')
            ? strtoupper(sanitize_text_field((string) $this->get_ticket_currency()))
            : strtoupper(sanitize_text_field((string) get_option('eep_ticket_currency', 'USD')));
        $formatted_amount = number_format($amount, 2, '.', '');
        if ($currency === '') {
            return $formatted_amount;
        }
        return trim($currency . ' ' . $formatted_amount);
    }


    private function sync_event_price_with_ticket_request($is_events_profile = false) {
        if (!$is_events_profile) {
            return;
        }
        $ticket_enabled = (string) wp_unslash($_POST['eep_ticket_sales_enabled'] ?? '0') === '1';
        if (!$ticket_enabled) {
            $_POST['eep_price'] = '';
            return;
        }
        $ticket_amount = $this->parse_ticket_price_amount_value(wp_unslash($_POST['eep_ticket_price'] ?? ''));
        $_POST['eep_price'] = $this->format_ticket_price_amount_label($ticket_amount);
    }


    private function get_event_price_from_ticket_meta($post_id, $fallback = '') {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return sanitize_text_field((string) $fallback);
        }
        $ticket_enabled = (string) get_post_meta($post_id, 'eep_ticket_sales_enabled', true) === '1';
        if (!$ticket_enabled) {
            return '';
        }
        $ticket_amount = $this->parse_ticket_price_amount_value((string) get_post_meta($post_id, 'eep_ticket_price', true));
        return $this->format_ticket_price_amount_label($ticket_amount);
    }


    private function is_sparse_autosave_value($value) {
        if (is_array($value)) {
            foreach ($value as $item) {
                if (!$this->is_sparse_autosave_value($item)) {
                    return false;
                }
            }
            return true;
        }
        if (is_bool($value) || is_int($value) || is_float($value)) {
            return false;
        }
        return trim(eep_scalar_to_string($value)) === '';
    }


    private function is_empty_json_collection_value($value) {
        $normalized = strtolower(trim(eep_scalar_to_string($value)));
        return in_array($normalized, ['', '[]', '{}', 'null'], true);
    }

    private function should_skip_sparse_meta_update($post_id, $meta_key, $value, $is_existing_update = false) {
        if (!$is_existing_update) {
            return false;
        }
        if (!$this->is_sparse_autosave_value($value)) {
            return false;
        }

        $meta_key = sanitize_key((string) $meta_key);
        $protected_sparse_keys = [
            'eep_start',
            'eep_end',
            'eep_cover_image_url',
            'eep_gallery_urls',
            'eep_video_urls',
            'eep_address',
            'eep_city',
            'eep_country',
            'eep_lat',
            'eep_lng',
        ];
        if (!in_array($meta_key, $protected_sparse_keys, true)) {
            return false;
        }

        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return false;
        }

        $existing_value = trim(eep_scalar_to_string(get_post_meta($post_id, $meta_key, true)));
        return $existing_value !== '';
    }


    private function sanitize_event_seo_title($value) {
        $title = sanitize_text_field((string) $value);
        if (function_exists('mb_substr')) {
            $title = mb_substr($title, 0, 60);
        } else {
            $title = substr($title, 0, 60);
        }
        return trim((string) $title);
    }


    private function sanitize_event_seo_description($value) {
        $description = eep_sanitize_textarea((string) $value);
        $description = trim((string) preg_replace('/\s+/u', ' ', (string) $description));
        if (function_exists('mb_substr')) {
            $description = mb_substr((string) $description, 0, 158);
        } else {
            $description = substr((string) $description, 0, 158);
        }
        return trim((string) $description);
    }


    private function build_submit_form_prefill($post_id = 0, $profile_key = '') {
        $post_id = absint($post_id);
        $custom_form_fields = [];
        $resolved_profile_key = $this->resolve_profile_key_for_post($post_id, $profile_key);
        $profile_ui = $this->get_site_profile_ui_labels($resolved_profile_key);
        $is_events_profile = (string) ($profile_ui['is_events_profile'] ?? '1') === '1';
        $email_defaults = $this->get_default_event_email_templates();
        $defaults = [
            'is_edit' => false,
            'edit_event_id' => 0,
            'post_title' => '',
            'post_content' => '',
            'post_excerpt' => '',
            'eep_cover_image_url' => '',
            'eep_gallery_urls' => '',
            'eep_video_urls' => '',
            'eep_contact_phone' => '',
            'eep_contact_email' => wp_get_current_user()->user_email ?? '',
            'eep_city' => '',
            'eep_address' => '',
            'eep_venue' => '',
            'eep_company_name' => '',
            'eep_country' => '',
            'eep_lat' => '',
            'eep_lng' => '',
            'eep_directory_business_type' => 'independent-business',
            'eep_directory_listing_status' => 'active',
            'eep_directory_contact_person' => '',
            'eep_directory_service_area' => '',
            'eep_directory_operating_hours' => '',
            'eep_directory_response_time' => '',
            'eep_directory_established_year' => '',
            'eep_directory_pricing_summary' => '',
            'eep_directory_appointment_url' => '',
            'eep_directory_whatsapp_url' => '',
            'eep_directory_services' => '',
            'eep_directory_highlights' => '',
            'eep_directory_certifications' => '',
            'eep_external_url' => '',
            'eep_event_stream_url' => '',
            'eep_job_type' => '',
            'eep_job_work_mode' => '',
            'eep_job_posted_date' => '',
            'eep_job_compensation_type' => '',
            'eep_job_contract_duration' => '',
            'eep_job_working_hours' => '',
            'eep_job_salary' => '',
            'eep_job_apply_url' => '',
            'eep_job_application_email' => '',
            'eep_job_closing_date' => '',
            'eep_job_department' => '',
            'eep_job_experience_level' => '',
            'eep_job_reporting_to' => '',
            'eep_job_remote_policy' => '',
            'eep_job_visa_sponsorship' => '0',
            'eep_job_open_positions' => '',
            'eep_job_interview_process' => '',
            'eep_job_expected_start_date' => '',
            'eep_job_skills' => '',
            'eep_job_responsibilities' => '',
            'eep_job_requirements' => '',
            'eep_job_benefits' => '',
            'eep_job_equity' => '',
            'eep_job_bonus' => '',
            'eep_job_recruitment_timeline' => '',
            'eep_job_company_intro_video' => '',
            'eep_job_role_overview_video' => '',
            'eep_job_company_website' => '',
            'eep_job_company_size' => '',
            'eep_job_industry' => '',
            'eep_market_condition' => '',
            'eep_market_price_type' => 'fixed',
            'eep_market_quantity' => '',
            'eep_market_sku' => '',
            'eep_market_brand' => '',
            'eep_market_model' => '',
            'eep_market_year' => '',
            'eep_market_seller_type' => 'private',
            'eep_market_listing_status' => 'active',
            'eep_market_expires_on' => '',
            'eep_market_delivery_methods' => '',
            'eep_market_returns_accepted' => '0',
            'eep_market_accept_offers' => '1',
            'eep_market_payment_methods' => '',
            'eep_market_condition_notes' => '',
            'eep_market_shipping_notes' => '',
            'eep_event_mode_option' => 'in-person',
            'eep_price_simple' => 'free',
            'event_type_term_id' => 0,
            'event_tags' => '',
            'eep_start_local' => '',
            'eep_end_local' => '',
            'eep_start_raw' => '',
            'eep_end_raw' => '',
            'eep_timezone' => '',
            'eep_capacity' => '',
            'eep_price' => '',
            'eep_ticket_sales_enabled' => '0',
            'eep_ticket_price' => '',
            'eep_ticket_button_label' => '',
            'eep_meta_title' => '',
            'eep_meta_description' => '',
            'eep_official_event' => '0',
            'eep_is_recurring' => '0',
            'eep_recurrence_until' => '',
            'eep_webapp_enabled' => (string) get_option('eep_event_webapp_default_enabled', '0') === '1' ? '1' : '0',
            'eep_webapp_title' => '',
            'eep_webapp_subtitle' => '',
            'eep_webapp_icon_url' => '',
            'eep_webapp_splash_url' => '',
            'eep_webapp_header_logo_url' => '',
            'eep_webapp_loading_bg_color' => '#ffffff',
            'eep_webapp_accent_color' => sanitize_hex_color((string) get_option('eep_event_webapp_default_accent_color', (string) get_option('eep_style_accent_color', '#4F46E5'))) ?: '#4F46E5',
            'eep_webapp_push_enabled' => (string) get_option('eep_event_webapp_push_default_enabled', '0') === '1' ? '1' : '0',
            'eep_webapp_push_provider' => 'none',
            'eep_webapp_push_key' => '',
            'eep_webapp_requires_login' => '1',
            'eep_webapp_allowed_user_ids' => '',
            'eep_webapp_chat_moderation' => 'open',
            'eep_webapp_qa_moderation' => 'open',
            'eep_webapp_qa_allow_attendee_answers' => '0',
            'eep_webapp_qa_allow_anonymous' => '0',
            'eep_webapp_polls_vote_mode' => 'single',
            'eep_webapp_enable_chat' => '1',
            'eep_webapp_enable_qa' => '1',
            'eep_webapp_enable_polls' => '1',
            'eep_webapp_enable_map' => '1',
            'eep_webapp_enable_notifications' => '1',
            'eep_webapp_enable_attendees' => '1',
            'eep_webapp_enable_custom_itinerary' => '1',
            'eep_webapp_asset_urls' => '',
            'eep_email_subject_confirmation' => (string) get_option('eep_email_subject_confirmation', (string) ($email_defaults['eep_email_subject_confirmation'] ?? '')),
            'eep_email_subject_reminder' => (string) get_option('eep_email_subject_reminder', (string) ($email_defaults['eep_email_subject_reminder'] ?? '')),
            'eep_email_subject_update' => (string) get_option('eep_email_subject_update', (string) ($email_defaults['eep_email_subject_update'] ?? '')),
            'eep_email_subject_cancellation' => (string) get_option('eep_email_subject_cancellation', (string) ($email_defaults['eep_email_subject_cancellation'] ?? '')),
            'eep_email_body_confirmation' => (string) get_option('eep_email_body_confirmation', (string) ($email_defaults['eep_email_body_confirmation'] ?? '')),
            'eep_email_body_reminder' => (string) get_option('eep_email_body_reminder', (string) ($email_defaults['eep_email_body_reminder'] ?? '')),
            'eep_email_body_update' => (string) get_option('eep_email_body_update', (string) ($email_defaults['eep_email_body_update'] ?? '')),
            'eep_email_body_cancellation' => (string) get_option('eep_email_body_cancellation', (string) ($email_defaults['eep_email_body_cancellation'] ?? '')),
            'sessions' => [],
            'key_speakers' => [],
            'custom_form_fields' => [],
            'custom_fields_values' => [],
            'custom_taxonomy_terms' => [],
        ];
        foreach (['confirmation', 'reminder', 'update', 'cancellation'] as $template_key) {
            $subject_option_key = 'eep_email_subject_' . $template_key;
            if (trim((string) ($defaults[$subject_option_key] ?? '')) === '') {
                $defaults[$subject_option_key] = (string) ($email_defaults[$subject_option_key] ?? '');
            }
            $body_option_key = 'eep_email_body_' . $template_key;
            if (trim((string) ($defaults[$body_option_key] ?? '')) === '') {
                $defaults[$body_option_key] = (string) ($email_defaults[$body_option_key] ?? '');
            }
        }
        if ($post_id <= 0) {
            return $defaults;
        }
        if (get_post_type($post_id) !== self::POST_TYPE) {
            return $defaults;
        }
        if (!$this->user_can_edit_event($post_id)) {
            $current_user_id = (int) get_current_user_id();
            if ($current_user_id <= 0 || !current_user_can('edit_post', $post_id)) {
                return $defaults;
            }
        }
        $prefill_target_post_id = $post_id;
        $post_id = $this->resolve_submit_prefill_source_post_id($post_id);

        $prefill = $defaults;
        $prefill['is_edit'] = true;
        $prefill['edit_event_id'] = $prefill_target_post_id;
        $prefill['post_title'] = (string) $this->recover_existing_event_title_value($post_id, (string) get_the_title($post_id));
        $prefill['post_content'] = (string) get_post_field('post_content', $post_id);
        $prefill['post_excerpt'] = (string) get_post_field('post_excerpt', $post_id);
        if (trim((string) $prefill['post_title']) === '' && trim((string) $prefill['post_content']) !== '') {
            $prefill['post_title'] = wp_trim_words(eep_strip_all_tags((string) $prefill['post_content']), 8, '');
        }
        if (trim((string) $prefill['post_title']) === '') {
            $prefill['post_title'] = sprintf(__('Untitled Listing #%d', 'nexdirectory'), $prefill_target_post_id);
        }
        if (
            trim((string) $prefill['post_title']) !== ''
            && trim((string) get_post_field('post_title', $prefill_target_post_id)) === ''
            && !wp_is_post_revision($prefill_target_post_id)
            && !wp_is_post_autosave($prefill_target_post_id)
        ) {
            wp_update_post([
                'ID' => $prefill_target_post_id,
                'post_title' => (string) $prefill['post_title'],
            ]);
        }
        if (trim($prefill['post_excerpt']) === '') {
            if (trim($prefill['post_content']) !== '') {
                $prefill['post_excerpt'] = wp_trim_words(eep_strip_all_tags((string) $prefill['post_content']), 26);
            }
        }
        $prefill['eep_cover_image_url'] = (string) $this->get_event_cover_image_url($post_id, 'full', 'thumbnail');
        if ($prefill['eep_cover_image_url'] === '' && has_post_thumbnail($post_id)) {
            $prefill['eep_cover_image_url'] = esc_url_raw((string) get_the_post_thumbnail_url($post_id, 'full'));
        }
        $prefill['eep_gallery_urls'] = implode("\n", $this->get_event_gallery_image_urls($post_id, 'thumbnail'));
        if ($prefill['eep_cover_image_url'] === '' && trim((string) $prefill['eep_gallery_urls']) !== '') {
            $gallery_rows = array_values(array_filter(array_map('trim', preg_split('/\r\n|\r|\n/', (string) $prefill['eep_gallery_urls']) ?: [])));
            if (!empty($gallery_rows)) {
                $prefill['eep_cover_image_url'] = esc_url_raw((string) $gallery_rows[0]);
            }
        }
        $prefill['eep_video_urls'] = (string) get_post_meta($post_id, 'eep_video_urls', true);
        $prefill['eep_contact_phone'] = (string) get_post_meta($post_id, 'eep_contact_phone', true);
        $prefill['eep_contact_email'] = (string) get_post_meta($post_id, 'eep_contact_email', true);
        if ($prefill['eep_contact_email'] === '') {
            $prefill['eep_contact_email'] = (string) (wp_get_current_user()->user_email ?? '');
        }
        $prefill['eep_city'] = (string) get_post_meta($post_id, 'eep_city', true);
        $prefill['eep_address'] = (string) get_post_meta($post_id, 'eep_address', true);
        $prefill['eep_venue'] = (string) get_post_meta($post_id, 'eep_venue', true);
        $prefill['eep_company_name'] = (string) get_post_meta($post_id, 'eep_company_name', true);
        $prefill['eep_country'] = (string) get_post_meta($post_id, 'eep_country', true);
        $prefill['eep_lat'] = (string) get_post_meta($post_id, 'eep_lat', true);
        $prefill['eep_lng'] = (string) get_post_meta($post_id, 'eep_lng', true);
        $prefill['eep_directory_business_type'] = (string) get_post_meta($post_id, 'eep_directory_business_type', true);
        $prefill['eep_directory_listing_status'] = (string) get_post_meta($post_id, 'eep_directory_listing_status', true);
        $prefill['eep_directory_contact_person'] = (string) get_post_meta($post_id, 'eep_directory_contact_person', true);
        $prefill['eep_directory_service_area'] = (string) get_post_meta($post_id, 'eep_directory_service_area', true);
        $prefill['eep_directory_operating_hours'] = (string) get_post_meta($post_id, 'eep_directory_operating_hours', true);
        $prefill['eep_directory_response_time'] = (string) get_post_meta($post_id, 'eep_directory_response_time', true);
        $prefill['eep_directory_established_year'] = (string) get_post_meta($post_id, 'eep_directory_established_year', true);
        $prefill['eep_directory_pricing_summary'] = (string) get_post_meta($post_id, 'eep_directory_pricing_summary', true);
        $prefill['eep_directory_appointment_url'] = (string) get_post_meta($post_id, 'eep_directory_appointment_url', true);
        $prefill['eep_directory_whatsapp_url'] = (string) get_post_meta($post_id, 'eep_directory_whatsapp_url', true);
        $prefill['eep_directory_services'] = (string) get_post_meta($post_id, 'eep_directory_services', true);
        $prefill['eep_directory_highlights'] = (string) get_post_meta($post_id, 'eep_directory_highlights', true);
        $prefill['eep_directory_certifications'] = (string) get_post_meta($post_id, 'eep_directory_certifications', true);
        $prefill['eep_external_url'] = (string) get_post_meta($post_id, 'eep_external_url', true);
        $prefill['eep_event_stream_url'] = (string) get_post_meta($post_id, 'eep_event_stream_url', true);
        $prefill['eep_job_type'] = (string) get_post_meta($post_id, 'eep_job_type', true);
        $prefill['eep_job_work_mode'] = (string) get_post_meta($post_id, 'eep_job_work_mode', true);
        $prefill['eep_job_posted_date'] = (string) get_post_meta($post_id, 'eep_job_posted_date', true);
        $prefill['eep_job_compensation_type'] = (string) get_post_meta($post_id, 'eep_job_compensation_type', true);
        $prefill['eep_job_contract_duration'] = (string) get_post_meta($post_id, 'eep_job_contract_duration', true);
        $prefill['eep_job_working_hours'] = (string) get_post_meta($post_id, 'eep_job_working_hours', true);
        $prefill['eep_job_salary'] = (string) get_post_meta($post_id, 'eep_job_salary', true);
        $prefill['eep_job_apply_url'] = (string) get_post_meta($post_id, 'eep_job_apply_url', true);
        $prefill['eep_job_application_email'] = (string) get_post_meta($post_id, 'eep_job_application_email', true);
        $prefill['eep_job_closing_date'] = (string) get_post_meta($post_id, 'eep_job_closing_date', true);
        $prefill['eep_job_department'] = (string) get_post_meta($post_id, 'eep_job_department', true);
        $prefill['eep_job_experience_level'] = (string) get_post_meta($post_id, 'eep_job_experience_level', true);
        $prefill['eep_job_reporting_to'] = (string) get_post_meta($post_id, 'eep_job_reporting_to', true);
        $prefill['eep_job_remote_policy'] = (string) get_post_meta($post_id, 'eep_job_remote_policy', true);
        $prefill['eep_job_visa_sponsorship'] = (string) get_post_meta($post_id, 'eep_job_visa_sponsorship', true);
        $prefill['eep_job_open_positions'] = (string) get_post_meta($post_id, 'eep_job_open_positions', true);
        $prefill['eep_job_interview_process'] = (string) get_post_meta($post_id, 'eep_job_interview_process', true);
        $prefill['eep_job_expected_start_date'] = (string) get_post_meta($post_id, 'eep_job_expected_start_date', true);
        $prefill['eep_job_skills'] = (string) get_post_meta($post_id, 'eep_job_skills', true);
        $prefill['eep_job_responsibilities'] = (string) get_post_meta($post_id, 'eep_job_responsibilities', true);
        $prefill['eep_job_requirements'] = (string) get_post_meta($post_id, 'eep_job_requirements', true);
        $prefill['eep_job_benefits'] = (string) get_post_meta($post_id, 'eep_job_benefits', true);
        $prefill['eep_job_equity'] = (string) get_post_meta($post_id, 'eep_job_equity', true);
        $prefill['eep_job_bonus'] = (string) get_post_meta($post_id, 'eep_job_bonus', true);
        $prefill['eep_job_recruitment_timeline'] = (string) get_post_meta($post_id, 'eep_job_recruitment_timeline', true);
        $prefill['eep_job_company_intro_video'] = (string) get_post_meta($post_id, 'eep_job_company_intro_video', true);
        $prefill['eep_job_role_overview_video'] = (string) get_post_meta($post_id, 'eep_job_role_overview_video', true);
        $prefill['eep_job_company_website'] = (string) get_post_meta($post_id, 'eep_job_company_website', true);
        $prefill['eep_job_company_size'] = (string) get_post_meta($post_id, 'eep_job_company_size', true);
        $prefill['eep_job_industry'] = (string) get_post_meta($post_id, 'eep_job_industry', true);
        $prefill['eep_market_condition'] = (string) get_post_meta($post_id, 'eep_market_condition', true);
        $prefill['eep_market_price_type'] = (string) get_post_meta($post_id, 'eep_market_price_type', true);
        $prefill['eep_market_quantity'] = (string) get_post_meta($post_id, 'eep_market_quantity', true);
        $prefill['eep_market_sku'] = (string) get_post_meta($post_id, 'eep_market_sku', true);
        $prefill['eep_market_brand'] = (string) get_post_meta($post_id, 'eep_market_brand', true);
        $prefill['eep_market_model'] = (string) get_post_meta($post_id, 'eep_market_model', true);
        $prefill['eep_market_year'] = (string) get_post_meta($post_id, 'eep_market_year', true);
        $prefill['eep_market_seller_type'] = (string) get_post_meta($post_id, 'eep_market_seller_type', true);
        $prefill['eep_market_listing_status'] = (string) get_post_meta($post_id, 'eep_market_listing_status', true);
        $prefill['eep_market_expires_on'] = (string) get_post_meta($post_id, 'eep_market_expires_on', true);
        $prefill['eep_market_delivery_methods'] = (string) get_post_meta($post_id, 'eep_market_delivery_methods', true);
        $prefill['eep_market_returns_accepted'] = (string) get_post_meta($post_id, 'eep_market_returns_accepted', true);
        $prefill['eep_market_accept_offers'] = (string) get_post_meta($post_id, 'eep_market_accept_offers', true);
        $prefill['eep_market_payment_methods'] = (string) get_post_meta($post_id, 'eep_market_payment_methods', true);
        $prefill['eep_market_condition_notes'] = (string) get_post_meta($post_id, 'eep_market_condition_notes', true);
        $prefill['eep_market_shipping_notes'] = (string) get_post_meta($post_id, 'eep_market_shipping_notes', true);
        $prefill['eep_start_raw'] = $this->normalize_prefill_datetime_meta_value((string) get_post_meta($post_id, 'eep_start', true));
        $prefill['eep_end_raw'] = $this->normalize_prefill_datetime_meta_value((string) get_post_meta($post_id, 'eep_end', true));
        if ($prefill['eep_market_expires_on'] === '' && preg_match('/^\d{4}-\d{2}-\d{2}/', (string) $prefill['eep_end_raw'], $market_end_match)) {
            $prefill['eep_market_expires_on'] = (string) ($market_end_match[0] ?? '');
        }
        $prefill['eep_start_local'] = $this->format_datetime_local_input((string) $prefill['eep_start_raw']);
        $prefill['eep_end_local'] = $this->format_datetime_local_input((string) $prefill['eep_end_raw']);
        $prefill['eep_timezone'] = (string) get_post_meta($post_id, 'eep_timezone', true);
        if ($prefill['eep_timezone'] === '') {
            $prefill['eep_timezone'] = $this->sanitize_timezone_value((string) get_option('timezone_string', ''));
        }
        $prefill['eep_capacity'] = (string) get_post_meta($post_id, 'eep_capacity', true);
        $prefill['eep_price'] = (string) get_post_meta($post_id, 'eep_price', true);
        $prefill['eep_ticket_sales_enabled'] = (string) get_post_meta($post_id, 'eep_ticket_sales_enabled', true);
        $prefill['eep_ticket_price'] = (string) get_post_meta($post_id, 'eep_ticket_price', true);
        $prefill['eep_ticket_button_label'] = (string) get_post_meta($post_id, 'eep_ticket_button_label', true);
        $prefill['eep_meta_title'] = (string) get_post_meta($post_id, 'eep_meta_title', true);
        $prefill['eep_meta_description'] = (string) get_post_meta($post_id, 'eep_meta_description', true);
        $requested_official_pending = (string) get_post_meta($post_id, 'eep_official_requested', true) === '1';
        $prefill['eep_official_event'] = ($this->get_official_event_flag($post_id) || $requested_official_pending) ? '1' : '0';
        $prefill['eep_recurrence_until'] = (string) get_post_meta($post_id, 'eep_recurrence_until', true);
        $prefill['eep_webapp_enabled'] = (string) get_post_meta($post_id, 'eep_webapp_enabled', true);
        $prefill['eep_webapp_title'] = (string) get_post_meta($post_id, 'eep_webapp_title', true);
        $prefill['eep_webapp_subtitle'] = (string) get_post_meta($post_id, 'eep_webapp_subtitle', true);
        $prefill['eep_webapp_icon_url'] = (string) get_post_meta($post_id, 'eep_webapp_icon_url', true);
        $prefill['eep_webapp_splash_url'] = (string) get_post_meta($post_id, 'eep_webapp_splash_url', true);
        $prefill['eep_webapp_header_logo_url'] = (string) get_post_meta($post_id, 'eep_webapp_header_logo_url', true);
        $prefill['eep_webapp_loading_bg_color'] = (string) get_post_meta($post_id, 'eep_webapp_loading_bg_color', true);
        $prefill['eep_webapp_accent_color'] = (string) get_post_meta($post_id, 'eep_webapp_accent_color', true);
        $prefill['eep_webapp_push_enabled'] = (string) get_post_meta($post_id, 'eep_webapp_push_enabled', true);
        $prefill['eep_webapp_push_provider'] = (string) get_post_meta($post_id, 'eep_webapp_push_provider', true);
        $prefill['eep_webapp_push_key'] = (string) get_post_meta($post_id, 'eep_webapp_push_key', true);
        $prefill['eep_webapp_requires_login'] = (string) get_post_meta($post_id, 'eep_webapp_requires_login', true);
        $prefill['eep_webapp_allowed_user_ids'] = (string) get_post_meta($post_id, 'eep_webapp_allowed_user_ids', true);
        $prefill['eep_webapp_chat_moderation'] = (string) get_post_meta($post_id, 'eep_webapp_chat_moderation', true);
        $prefill['eep_webapp_qa_moderation'] = (string) get_post_meta($post_id, 'eep_webapp_qa_moderation', true);
        $prefill['eep_webapp_qa_allow_attendee_answers'] = (string) get_post_meta($post_id, 'eep_webapp_qa_allow_attendee_answers', true);
        $prefill['eep_webapp_qa_allow_anonymous'] = (string) get_post_meta($post_id, 'eep_webapp_qa_allow_anonymous', true);
        $prefill['eep_webapp_polls_vote_mode'] = (string) get_post_meta($post_id, 'eep_webapp_polls_vote_mode', true);
        $prefill['eep_webapp_enable_chat'] = (string) get_post_meta($post_id, 'eep_webapp_enable_chat', true);
        $prefill['eep_webapp_enable_qa'] = (string) get_post_meta($post_id, 'eep_webapp_enable_qa', true);
        $prefill['eep_webapp_enable_polls'] = (string) get_post_meta($post_id, 'eep_webapp_enable_polls', true);
        $prefill['eep_webapp_enable_map'] = (string) get_post_meta($post_id, 'eep_webapp_enable_map', true);
        $prefill['eep_webapp_enable_notifications'] = (string) get_post_meta($post_id, 'eep_webapp_enable_notifications', true);
        $prefill['eep_webapp_enable_attendees'] = (string) get_post_meta($post_id, 'eep_webapp_enable_attendees', true);
        $prefill['eep_webapp_enable_custom_itinerary'] = (string) get_post_meta($post_id, 'eep_webapp_enable_custom_itinerary', true);
        $prefill['eep_webapp_asset_urls'] = (string) get_post_meta($post_id, 'eep_webapp_asset_urls', true);
        $prefill['eep_email_subject_confirmation'] = (string) get_post_meta($post_id, 'eep_email_subject_confirmation', true);
        $prefill['eep_email_subject_reminder'] = (string) get_post_meta($post_id, 'eep_email_subject_reminder', true);
        $prefill['eep_email_subject_update'] = (string) get_post_meta($post_id, 'eep_email_subject_update', true);
        $prefill['eep_email_subject_cancellation'] = (string) get_post_meta($post_id, 'eep_email_subject_cancellation', true);
        $prefill['eep_email_body_confirmation'] = (string) get_post_meta($post_id, 'eep_email_body_confirmation', true);
        $prefill['eep_email_body_reminder'] = (string) get_post_meta($post_id, 'eep_email_body_reminder', true);
        $prefill['eep_email_body_update'] = (string) get_post_meta($post_id, 'eep_email_body_update', true);
        $prefill['eep_email_body_cancellation'] = (string) get_post_meta($post_id, 'eep_email_body_cancellation', true);
        if ($prefill['eep_market_price_type'] === '') {
            $prefill['eep_market_price_type'] = 'fixed';
        }
        if ($prefill['eep_market_seller_type'] === '') {
            $prefill['eep_market_seller_type'] = 'private';
        }
        if ($prefill['eep_market_listing_status'] === '') {
            $prefill['eep_market_listing_status'] = 'active';
        }
        if ($prefill['eep_directory_business_type'] === '') {
            $prefill['eep_directory_business_type'] = 'independent-business';
        }
        if ($prefill['eep_directory_listing_status'] === '') {
            $prefill['eep_directory_listing_status'] = 'active';
        }
        if ($prefill['eep_market_returns_accepted'] !== '1') {
            $prefill['eep_market_returns_accepted'] = '0';
        }
        if ($prefill['eep_market_accept_offers'] !== '0') {
            $prefill['eep_market_accept_offers'] = '1';
        }
        if ($prefill['eep_ticket_sales_enabled'] !== '1') {
            $prefill['eep_ticket_sales_enabled'] = '0';
        }
        if ($prefill['eep_webapp_enabled'] !== '1') {
            $prefill['eep_webapp_enabled'] = $defaults['eep_webapp_enabled'];
        }
        if ($prefill['eep_webapp_push_enabled'] !== '1') {
            $prefill['eep_webapp_push_enabled'] = '0';
        }
        if ((string) $prefill['eep_webapp_requires_login'] === '') {
            $prefill['eep_webapp_requires_login'] = (string) ($defaults['eep_webapp_requires_login'] ?? '1');
        }
        if ($prefill['eep_webapp_requires_login'] !== '0') {
            $prefill['eep_webapp_requires_login'] = '1';
        }
        $prefill['eep_webapp_allowed_user_ids'] = implode(',', $this->parse_event_webapp_user_ids((string) ($prefill['eep_webapp_allowed_user_ids'] ?? '')));
        if (!in_array((string) $prefill['eep_webapp_push_provider'], ['none', 'onesignal', 'firebase', 'custom'], true)) {
            $prefill['eep_webapp_push_provider'] = 'none';
        }
        if (!in_array((string) $prefill['eep_webapp_chat_moderation'], ['open', 'moderated', 'admins-only'], true)) {
            $prefill['eep_webapp_chat_moderation'] = 'open';
        }
        if (!in_array((string) $prefill['eep_webapp_qa_moderation'], ['open', 'moderated', 'admins-only'], true)) {
            $prefill['eep_webapp_qa_moderation'] = 'open';
        }
        if (!in_array((string) $prefill['eep_webapp_polls_vote_mode'], ['single', 'multiple'], true)) {
            $prefill['eep_webapp_polls_vote_mode'] = 'single';
        }
        foreach ([
            'eep_webapp_enable_chat',
            'eep_webapp_enable_qa',
            'eep_webapp_enable_polls',
            'eep_webapp_enable_map',
            'eep_webapp_enable_notifications',
            'eep_webapp_enable_attendees',
            'eep_webapp_enable_custom_itinerary',
        ] as $webapp_toggle_key) {
            if ((string) ($prefill[$webapp_toggle_key] ?? '') !== '0') {
                $prefill[$webapp_toggle_key] = '1';
            }
        }
        foreach ([
            'eep_webapp_qa_allow_attendee_answers',
            'eep_webapp_qa_allow_anonymous',
        ] as $qa_toggle_key) {
            $prefill[$qa_toggle_key] = (string) ($prefill[$qa_toggle_key] ?? '0') === '1' ? '1' : '0';
        }
        $webapp_accent = sanitize_hex_color((string) $prefill['eep_webapp_accent_color']);
        if ($webapp_accent === null || $webapp_accent === '') {
            $webapp_accent = sanitize_hex_color((string) ($defaults['eep_webapp_accent_color'] ?? '#4F46E5')) ?: '#4F46E5';
        }
        $prefill['eep_webapp_accent_color'] = $webapp_accent;
        $webapp_loading_bg = sanitize_hex_color((string) ($prefill['eep_webapp_loading_bg_color'] ?? ''));
        if ($webapp_loading_bg === null || $webapp_loading_bg === '') {
            $webapp_loading_bg = sanitize_hex_color((string) ($defaults['eep_webapp_loading_bg_color'] ?? '#ffffff')) ?: '#ffffff';
        }
        $prefill['eep_webapp_loading_bg_color'] = $webapp_loading_bg;
        foreach (['confirmation', 'reminder', 'update', 'cancellation'] as $template_key) {
            $subject_option_key = 'eep_email_subject_' . $template_key;
            if (trim((string) $prefill[$subject_option_key]) === '') {
                $subject_default = (string) ($email_defaults[$subject_option_key] ?? '');
                $subject_value = trim((string) get_option($subject_option_key, $subject_default));
                $prefill[$subject_option_key] = $subject_value !== '' ? $subject_value : $subject_default;
            }
            $body_option_key = 'eep_email_body_' . $template_key;
            if (trim((string) $prefill[$body_option_key]) === '') {
                $body_default = (string) ($email_defaults[$body_option_key] ?? '');
                $body_value = trim((string) get_option($body_option_key, $body_default));
                $prefill[$body_option_key] = $body_value !== '' ? $body_value : $body_default;
            }
        }

        $saved_price_simple = sanitize_key((string) get_post_meta($post_id, 'eep_price_simple', true));
        if (in_array($saved_price_simple, ['free', 'paid'], true)) {
            $prefill['eep_price_simple'] = $saved_price_simple;
        }

        $mode_terms = wp_get_post_terms($post_id, self::TAX_MODE, ['fields' => 'all']);
        if (!is_wp_error($mode_terms) && !empty($mode_terms)) {
            $mode_names = strtolower(implode(' ', array_map(static function ($term) {
                return (string) ($term->name ?? '');
            }, $mode_terms)));
            if (strpos($mode_names, 'online') !== false || strpos($mode_names, 'stream') !== false || strpos($mode_names, 'video') !== false || strpos($mode_names, 'hybrid') !== false) {
                $prefill['eep_event_mode_option'] = 'video-stream';
            }
        }

        $price_terms = wp_get_post_terms($post_id, self::TAX_PRICE, ['fields' => 'all']);
        if (!is_wp_error($price_terms) && !empty($price_terms) && $saved_price_simple === '') {
            $price_names = strtolower(implode(' ', array_map(static function ($term) {
                return (string) ($term->name ?? '');
            }, $price_terms)));
            if (strpos($price_names, 'paid') !== false || strpos($price_names, 'premium') !== false || strpos($price_names, 'cost') !== false) {
                $prefill['eep_price_simple'] = 'paid';
            }
        }

        $type_ids = wp_get_post_terms($post_id, self::TAX_TYPE, ['fields' => 'ids']);
        if (!is_wp_error($type_ids) && !empty($type_ids)) {
            $prefill['event_type_term_id'] = (int) $type_ids[0];
        }
        $tag_names = wp_get_post_terms($post_id, self::TAX_TAG, ['fields' => 'names']);
        if (!is_wp_error($tag_names) && !empty($tag_names)) {
            $prefill['event_tags'] = implode(', ', array_map('eep_sanitize_text', $tag_names));
        }

        $recurrence = sanitize_key((string) get_post_meta($post_id, 'eep_recurrence', true));
        $sessions_raw = json_decode((string) get_post_meta($post_id, 'eep_sessions', true), true);
        $sessions = [];
        if (is_array($sessions_raw)) {
            foreach ($sessions_raw as $session) {
                if (!is_array($session)) {
                    continue;
                }
                $sessions[] = [
                    'title' => sanitize_text_field((string) ($session['title'] ?? '')),
                    'start' => $this->format_datetime_local_input((string) ($session['start'] ?? '')),
                    'end' => $this->format_datetime_local_input((string) ($session['end'] ?? '')),
                    'location' => sanitize_text_field((string) ($session['location'] ?? '')),
                    'speaker' => sanitize_text_field((string) ($session['speaker'] ?? '')),
                    'day' => sanitize_text_field((string) ($session['day'] ?? '')),
                    'notes' => eep_sanitize_textarea((string) ($session['notes'] ?? '')),
                    'attachments' => implode("\n", array_values(array_filter(array_map('esc_url_raw', (array) ($session['attachments'] ?? []))))),
                    'session_key' => sanitize_key((string) ($session['session_key'] ?? '')),
                ];
            }
        }
        $key_speakers = $this->parse_key_speakers((string) get_post_meta($post_id, 'eep_key_speakers', true));
        if ($is_events_profile) {
            if (!empty($sessions) || ($recurrence !== '' && $recurrence !== 'none')) {
                $prefill['eep_is_recurring'] = '1';
                $prefill['sessions'] = $sessions;
            }
            $prefill['key_speakers'] = $key_speakers;
        } else {
            $prefill['eep_is_recurring'] = '0';
            $prefill['eep_recurrence_until'] = '';
            $prefill['sessions'] = [];
            $prefill['key_speakers'] = [];
        }
        $prefill['custom_form_fields'] = $this->get_post_custom_field_schema($post_id, true);
        $custom_form_fields = $prefill['custom_form_fields'];
        $prefill['custom_fields_values'] = $this->get_post_custom_field_values($post_id, $custom_form_fields);
        foreach ($this->get_custom_taxonomy_definitions_for_submit() as $taxonomy_row) {
            $taxonomy_slug = sanitize_key((string) ($taxonomy_row['slug'] ?? ''));
            if ($taxonomy_slug === '' || !taxonomy_exists($taxonomy_slug)) {
                continue;
            }
            $term_ids = wp_get_post_terms($post_id, $taxonomy_slug, ['fields' => 'ids']);
            if (is_wp_error($term_ids)) {
                continue;
            }
            $prefill['custom_taxonomy_terms'][$taxonomy_slug] = array_values(array_filter(array_map('absint', (array) $term_ids)));
        }

        return $prefill;
    }


    private function normalize_submitted_youtube_id($value) {
        $normalized = preg_replace('/[^A-Za-z0-9_-]/', '', (string) $value);
        return substr((string) $normalized, 0, 32);
    }


    private function save_custom_taxonomy_terms_from_request($post_id) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return;
        }
        foreach ($this->get_custom_taxonomy_definitions_for_submit() as $taxonomy_row) {
            $taxonomy_slug = sanitize_key((string) ($taxonomy_row['slug'] ?? ''));
            if ($taxonomy_slug === '' || !taxonomy_exists($taxonomy_slug)) {
                continue;
            }
            $field_name = 'eep_tax_' . $taxonomy_slug;
            $raw_values = isset($_POST[$field_name]) ? wp_unslash($_POST[$field_name]) : [];
            if (!is_array($raw_values)) {
                $raw_values = [$raw_values];
            }
            $raw_values = array_values(array_filter(array_map(static function ($value) {
                return sanitize_text_field((string) $value);
            }, $raw_values), static function ($value) {
                return $value !== '';
            }));
            if (empty($raw_values)) {
                wp_set_post_terms($post_id, [], $taxonomy_slug, false);
                continue;
            }
            $this->set_terms_from_values($post_id, $taxonomy_slug, $raw_values);
        }
    }


    private function get_or_create_term_id($taxonomy, $name) {
        $name = trim((string) $name);
        if ($name === '') {
            return 0;
        }
        $term = get_term_by('name', $name, $taxonomy);
        if ($term && !is_wp_error($term)) {
            return (int) $term->term_id;
        }
        $created = wp_insert_term($name, $taxonomy);
        if (is_wp_error($created)) {
            return 0;
        }
        return (int) ($created['term_id'] ?? 0);
    }


    private function build_video_urls_from_fields($stream_url, $youtube_id_one, $youtube_id_two, $raw_video_urls = '') {
        $urls = [];
        $stream_url = esc_url_raw((string) $stream_url);
        if ($stream_url !== '') {
            $urls[] = $stream_url;
        }
        if ($youtube_id_one !== '') {
            $urls[] = 'https://www.youtube.com/watch?v=' . $youtube_id_one;
        }
        if ($youtube_id_two !== '') {
            $urls[] = 'https://www.youtube.com/watch?v=' . $youtube_id_two;
        }
        foreach ($this->parse_multiline_urls((string) $raw_video_urls) as $existing) {
            $urls[] = esc_url_raw((string) $existing);
        }
        return implode("\n", array_values(array_unique(array_filter($urls))));
    }


    private function parse_event_tags_input($raw_input, $max_tags = 8, $max_length = 40) {
        $max_tags = max(1, (int) $max_tags);
        $max_length = max(1, (int) $max_length);
        $raw_input = (string) $raw_input;
        if (trim($raw_input) === '') {
            return [];
        }

        $parts = array_map(static function ($item) {
            return sanitize_text_field((string) $item);
        }, explode(',', $raw_input));
        $parts = array_values(array_filter(array_map('trim', $parts), static function ($item) {
            return $item !== '';
        }));

        if (count($parts) > $max_tags) {
            return new WP_Error(
                'eep_tags_limit',
                sprintf(__('Use up to %d tags (comma separated).', 'nexdirectory'), $max_tags)
            );
        }

        foreach ($parts as $tag) {
            $length = function_exists('mb_strlen') ? mb_strlen($tag) : strlen($tag);
            if ($length > $max_length) {
                return new WP_Error(
                    'eep_tag_length',
                    sprintf(__('Each tag must be %d characters or fewer.', 'nexdirectory'), $max_length)
                );
            }
        }

        return array_values(array_unique($parts));
    }


    private function hydrate_event_datetime_fields_from_request() {
        $start_date_raw = wp_unslash($_POST['eep_start_date'] ?? '');
        $start_time_raw = wp_unslash($_POST['eep_start_time'] ?? '');
        $end_date_raw = wp_unslash($_POST['eep_end_date'] ?? '');
        $end_time_raw = wp_unslash($_POST['eep_end_time'] ?? '');

        if ((string) $start_date_raw !== '' || (string) $start_time_raw !== '') {
            $_POST['eep_start'] = $this->build_event_datetime_from_inputs($start_date_raw, $start_time_raw);
        } else {
            $_POST['eep_start'] = '';
        }

        if ((string) $end_date_raw === '' && (string) $end_time_raw !== '' && (string) $_POST['eep_start'] !== '') {
            $start_date_fallback = substr((string) $_POST['eep_start'], 0, 10);
            if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $start_date_fallback) === 1) {
                $end_date_raw = $start_date_fallback;
            }
        }

        if ((string) $end_date_raw !== '' || (string) $end_time_raw !== '') {
            $_POST['eep_end'] = $this->build_event_datetime_from_inputs($end_date_raw, $end_time_raw);
        } else {
            $_POST['eep_end'] = '';
        }

    }


    private function maybe_fill_request_coordinates_from_location_fields() {
        $lat_raw = (string) wp_unslash($_POST['eep_lat'] ?? '');
        $lng_raw = (string) wp_unslash($_POST['eep_lng'] ?? '');
        if (is_numeric($lat_raw) && is_numeric($lng_raw)) {
            return;
        }

        $address = sanitize_text_field((string) wp_unslash($_POST['eep_address'] ?? ''));
        $city = sanitize_text_field((string) wp_unslash($_POST['eep_city'] ?? ''));
        $country = sanitize_text_field((string) wp_unslash($_POST['eep_country'] ?? ''));
        $venue = sanitize_text_field((string) wp_unslash($_POST['eep_venue'] ?? ''));

        $geo = $this->geocode_event_location_from_parts($address, $city, $country, $venue);
        if (!is_array($geo)) {
            return;
        }

        if (!is_numeric($lat_raw) && isset($geo['lat']) && is_numeric($geo['lat'])) {
            $_POST['eep_lat'] = (string) $geo['lat'];
        }
        if (!is_numeric($lng_raw) && isset($geo['lng']) && is_numeric($geo['lng'])) {
            $_POST['eep_lng'] = (string) $geo['lng'];
        }
        if ($city === '' && !empty($geo['city'])) {
            $_POST['eep_city'] = sanitize_text_field((string) $geo['city']);
        }
        if ($country === '' && !empty($geo['country'])) {
            $_POST['eep_country'] = sanitize_text_field((string) $geo['country']);
        }
        if ($address === '' && !empty($geo['address'])) {
            $_POST['eep_address'] = sanitize_text_field((string) $geo['address']);
        }
    }


    public function handle_submit_event() {
        $is_ajax_submit = (string) wp_unslash($_POST['eep_ajax'] ?? '') === '1';
        $edit_post_id = isset($_POST['eep_edit_post_id']) ? absint($_POST['eep_edit_post_id']) : 0;
        $autosave_post_id = isset($_POST['eep_autosave_post_id']) ? absint($_POST['eep_autosave_post_id']) : 0;
        $profile_source_post_id = $edit_post_id > 0 ? $edit_post_id : $autosave_post_id;
        $profile_key = $this->resolve_profile_key_for_post($profile_source_post_id);
        $ui_labels = $this->get_site_profile_ui_labels($profile_key);
        $profile_key = sanitize_key((string) ($ui_labels['profile_key'] ?? $profile_key));
        $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';
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $singular_lower = sanitize_text_field((string) ($ui_labels['singular_lower'] ?? strtolower($singular_label)));

        if (!is_user_logged_in()) {
            wp_die(__('You must be logged in.', 'nexdirectory'));
        }
        $current_user_id = (int) get_current_user_id();
        $is_existing_edit_request = $edit_post_id > 0 && $this->user_can_edit_event($edit_post_id, $current_user_id);
        if (!$this->user_can_submit_events($current_user_id) && !$is_existing_edit_request) {
            wp_die(sprintf(
                /* translators: %s: singular listing label */
                __('You do not have permission to submit %s.', 'nexdirectory'),
                strtolower($singular_label)
            ));
        }
        if (!$is_existing_edit_request && !$this->can_create_more_events($current_user_id, 1)) {
            $limit_status = (array) $this->get_demo_event_limit_status();
            $limit = max(1, (int) ($limit_status['limit'] ?? 3));
            $used = max(0, (int) ($limit_status['used'] ?? $limit));
            $demo_limit_message = sprintf(
                __('Demo mode includes %1$d free events. You currently have %2$d. Upgrade or activate your license to add more.', 'nexdirectory'),
                $limit,
                $used
            );
            if ($is_ajax_submit) {
                wp_send_json_error([
                    'message' => $demo_limit_message,
                    'code' => 'demo_event_limit_reached',
                    'limit' => $limit,
                    'used' => $used,
                ], 403);
            }
            $this->redirect_with_message(
                wp_get_referer() ?: home_url('/'),
                $demo_limit_message,
                'error'
            );
        }
        if (!$is_existing_edit_request && !$this->user_verified_for_first_submission($current_user_id)) {
            $resent = false;
            $last_sent = (int) get_user_meta($current_user_id, 'eep_email_verify_last_sent', true);
            if ($last_sent <= 0 || (time() - $last_sent) > 300) {
                $resent = (bool) $this->send_verification_email($current_user_id);
                if ($resent) {
                    update_user_meta($current_user_id, 'eep_email_verify_last_sent', (string) time());
                }
            }

            $this->redirect_with_message(
                wp_get_referer() ?: home_url('/'),
                $resent
                    ? __('Verify your email before first submission. A verification email was sent to your account inbox.', 'nexdirectory')
                    : __('Verify your email before first submission. Check your inbox or resend verification from account settings.', 'nexdirectory'),
                'error'
            );
        }

        if (!isset($_POST['eep_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_nonce'])), 'eep_submit_event_nonce')) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }

        if (!empty($_POST['eep_website'])) {
            $this->increment_spam_counter();
            wp_die(__('Spam check failed.', 'nexdirectory'));
        }

        if ($this->is_submission_rate_limited()) {
            $this->increment_spam_counter();
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Too many attempts. Please wait and try again.', 'nexdirectory'), 'error');
        }
        if ($this->should_enforce_submit_form_recaptcha() && !$this->verify_recaptcha_token(wp_unslash($_POST['eep_recaptcha_token'] ?? ''))) {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('reCAPTCHA validation failed. Please try again.', 'nexdirectory'), 'error');
        }

        $this->hydrate_event_datetime_fields_from_request();
        $_POST['eep_official_event'] = $this->normalize_official_event_value(wp_unslash($_POST['eep_official_event'] ?? '0'));
        $requested_official_value = (string) $_POST['eep_official_event'];
        $_POST['eep_timezone'] = $this->sanitize_timezone_value(wp_unslash($_POST['eep_timezone'] ?? ''));
        $_POST['eep_sessions'] = wp_unslash($_POST['eep_sessions'] ?? '[]');
        $_POST['eep_key_speakers'] = wp_unslash($_POST['eep_key_speakers'] ?? '[]');
        $_POST['eep_recurrence'] = sanitize_key((string) wp_unslash($_POST['eep_recurrence'] ?? 'none'));
        $_POST['eep_recurrence_until'] = sanitize_text_field((string) wp_unslash($_POST['eep_recurrence_until'] ?? ''));
        $_POST['eep_lock_pin'] = (string) (wp_unslash($_POST['eep_lock_pin'] ?? '0')) === '1' ? '1' : '0';
        $_POST['eep_price_simple'] = sanitize_key((string) wp_unslash($_POST['eep_price_simple'] ?? 'free'));
        $_POST['eep_event_stream_url'] = esc_url_raw((string) wp_unslash($_POST['eep_event_stream_url'] ?? ''));
        $_POST['eep_meta_title'] = $this->sanitize_event_seo_title(wp_unslash($_POST['eep_meta_title'] ?? ''));
        $_POST['eep_meta_description'] = $this->sanitize_event_seo_description(wp_unslash($_POST['eep_meta_description'] ?? ''));
        if ((string) get_option('eep_enable_event_seo_fields', '1') !== '1') {
            unset($_POST['eep_meta_title'], $_POST['eep_meta_description']);
        }
        $_POST['eep_youtube_video_id_1'] = $this->normalize_submitted_youtube_id(wp_unslash($_POST['eep_youtube_video_id_1'] ?? ''));
        $_POST['eep_youtube_video_id_2'] = $this->normalize_submitted_youtube_id(wp_unslash($_POST['eep_youtube_video_id_2'] ?? ''));
        $_POST['eep_company_name'] = sanitize_text_field((string) wp_unslash($_POST['eep_company_name'] ?? ''));
        $_POST['eep_directory_business_type'] = sanitize_key((string) wp_unslash($_POST['eep_directory_business_type'] ?? 'independent-business'));
        $_POST['eep_directory_listing_status'] = sanitize_key((string) wp_unslash($_POST['eep_directory_listing_status'] ?? 'active'));
        $_POST['eep_directory_contact_person'] = sanitize_text_field((string) wp_unslash($_POST['eep_directory_contact_person'] ?? ''));
        $_POST['eep_directory_service_area'] = sanitize_text_field((string) wp_unslash($_POST['eep_directory_service_area'] ?? ''));
        $_POST['eep_directory_operating_hours'] = sanitize_text_field((string) wp_unslash($_POST['eep_directory_operating_hours'] ?? ''));
        $_POST['eep_directory_response_time'] = sanitize_text_field((string) wp_unslash($_POST['eep_directory_response_time'] ?? ''));
        $_POST['eep_directory_established_year'] = absint((int) wp_unslash($_POST['eep_directory_established_year'] ?? 0));
        $_POST['eep_directory_pricing_summary'] = sanitize_text_field((string) wp_unslash($_POST['eep_directory_pricing_summary'] ?? ''));
        $_POST['eep_directory_appointment_url'] = esc_url_raw((string) wp_unslash($_POST['eep_directory_appointment_url'] ?? ''));
        $_POST['eep_directory_whatsapp_url'] = esc_url_raw((string) wp_unslash($_POST['eep_directory_whatsapp_url'] ?? ''));
        $_POST['eep_directory_services'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_directory_services'] ?? ''));
        $_POST['eep_directory_highlights'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_directory_highlights'] ?? ''));
        $_POST['eep_directory_certifications'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_directory_certifications'] ?? ''));
        $_POST['eep_job_type'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_type'] ?? ''));
        $_POST['eep_job_work_mode'] = sanitize_key((string) wp_unslash($_POST['eep_job_work_mode'] ?? ''));
        $_POST['eep_job_posted_date'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_posted_date'] ?? ''));
        $_POST['eep_job_compensation_type'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_compensation_type'] ?? ''));
        $_POST['eep_job_contract_duration'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_contract_duration'] ?? ''));
        $_POST['eep_job_working_hours'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_working_hours'] ?? ''));
        $_POST['eep_job_salary'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_salary'] ?? ''));
        $_POST['eep_job_apply_url'] = esc_url_raw((string) wp_unslash($_POST['eep_job_apply_url'] ?? ''));
        $_POST['eep_job_application_email'] = sanitize_email((string) wp_unslash($_POST['eep_job_application_email'] ?? ''));
        $_POST['eep_job_closing_date'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_closing_date'] ?? ''));
        $_POST['eep_job_department'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_department'] ?? ''));
        $_POST['eep_job_experience_level'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_experience_level'] ?? ''));
        $_POST['eep_job_reporting_to'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_reporting_to'] ?? ''));
        $_POST['eep_job_remote_policy'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_remote_policy'] ?? ''));
        $_POST['eep_job_visa_sponsorship'] = ((string) wp_unslash($_POST['eep_job_visa_sponsorship'] ?? '0')) === '1' ? '1' : '0';
        $_POST['eep_job_open_positions'] = absint((int) wp_unslash($_POST['eep_job_open_positions'] ?? 0));
        $_POST['eep_job_interview_process'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_job_interview_process'] ?? ''));
        $_POST['eep_job_expected_start_date'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_expected_start_date'] ?? ''));
        $_POST['eep_job_skills'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_skills'] ?? ''));
        $_POST['eep_job_responsibilities'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_job_responsibilities'] ?? ''));
        $_POST['eep_job_requirements'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_job_requirements'] ?? ''));
        $_POST['eep_job_benefits'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_job_benefits'] ?? ''));
        $_POST['eep_job_equity'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_equity'] ?? ''));
        $_POST['eep_job_bonus'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_bonus'] ?? ''));
        $_POST['eep_job_recruitment_timeline'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_job_recruitment_timeline'] ?? ''));
        $_POST['eep_job_company_intro_video'] = esc_url_raw((string) wp_unslash($_POST['eep_job_company_intro_video'] ?? ''));
        $_POST['eep_job_role_overview_video'] = esc_url_raw((string) wp_unslash($_POST['eep_job_role_overview_video'] ?? ''));
        $_POST['eep_job_company_website'] = esc_url_raw((string) wp_unslash($_POST['eep_job_company_website'] ?? ''));
        $_POST['eep_job_company_size'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_company_size'] ?? ''));
        $_POST['eep_job_industry'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_industry'] ?? ''));
        $_POST['eep_market_condition'] = sanitize_text_field((string) wp_unslash($_POST['eep_market_condition'] ?? ''));
        $_POST['eep_market_price_type'] = sanitize_key((string) wp_unslash($_POST['eep_market_price_type'] ?? 'fixed'));
        $_POST['eep_market_quantity'] = absint((int) wp_unslash($_POST['eep_market_quantity'] ?? 0));
        $_POST['eep_market_sku'] = sanitize_text_field((string) wp_unslash($_POST['eep_market_sku'] ?? ''));
        $_POST['eep_market_brand'] = sanitize_text_field((string) wp_unslash($_POST['eep_market_brand'] ?? ''));
        $_POST['eep_market_model'] = sanitize_text_field((string) wp_unslash($_POST['eep_market_model'] ?? ''));
        $_POST['eep_market_year'] = absint((int) wp_unslash($_POST['eep_market_year'] ?? 0));
        $_POST['eep_market_seller_type'] = sanitize_key((string) wp_unslash($_POST['eep_market_seller_type'] ?? 'private'));
        $_POST['eep_market_listing_status'] = sanitize_key((string) wp_unslash($_POST['eep_market_listing_status'] ?? 'active'));
        $_POST['eep_market_expires_on'] = sanitize_text_field((string) wp_unslash($_POST['eep_market_expires_on'] ?? ''));
        $_POST['eep_market_returns_accepted'] = ((string) wp_unslash($_POST['eep_market_returns_accepted'] ?? '0')) === '1' ? '1' : '0';
        $_POST['eep_market_accept_offers'] = ((string) wp_unslash($_POST['eep_market_accept_offers'] ?? '1')) === '0' ? '0' : '1';
        $_POST['eep_market_payment_methods'] = sanitize_text_field((string) wp_unslash($_POST['eep_market_payment_methods'] ?? ''));
        $_POST['eep_market_condition_notes'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_market_condition_notes'] ?? ''));
        $_POST['eep_market_shipping_notes'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_market_shipping_notes'] ?? ''));
        $market_delivery_methods_raw = wp_unslash($_POST['eep_market_delivery_methods'] ?? []);
        if (!is_array($market_delivery_methods_raw)) {
            $market_delivery_methods_raw = preg_split('/[\s,]+/', (string) $market_delivery_methods_raw);
            if (!is_array($market_delivery_methods_raw)) {
                $market_delivery_methods_raw = [];
            }
        }
        $allowed_market_delivery_methods = ['pickup', 'shipping', 'local-delivery', 'digital'];
        $market_delivery_methods = array_values(array_unique(array_filter(array_map(static function ($method) use ($allowed_market_delivery_methods) {
            $normalized = sanitize_key((string) $method);
            return in_array($normalized, $allowed_market_delivery_methods, true) ? $normalized : '';
        }, $market_delivery_methods_raw))));
        $_POST['eep_market_delivery_methods'] = implode(',', $market_delivery_methods);
        $_POST['eep_listing_profile'] = $profile_key;
        $this->sync_event_price_with_ticket_request($is_events_profile);
        if ($is_job_profile && trim((string) wp_unslash($_POST['eep_external_url'] ?? '')) === '' && (string) $_POST['eep_job_company_website'] !== '') {
            $_POST['eep_external_url'] = (string) $_POST['eep_job_company_website'];
        }

        $intent = sanitize_key(wp_unslash($_POST['eep_intent'] ?? 'pending'));
        $post_status = in_array($intent, ['draft', 'preview'], true) ? 'draft' : 'pending';
        $requires_complete_submission = !in_array($intent, ['draft', 'preview'], true);
        $is_edit_mode = false;
        if ($edit_post_id > 0) {
            if (!$this->user_can_edit_event($edit_post_id)) {
                $this->redirect_with_message(
                    wp_get_referer() ?: home_url('/'),
                    sprintf(__('You can only edit your own %s.', 'nexdirectory'), $singular_lower),
                    'error'
                );
            }
            $is_edit_mode = true;
        }
        $existing_post_id = 0;
        if ($is_edit_mode) {
            $existing_post_id = $edit_post_id;
        } elseif ($autosave_post_id > 0 && $this->user_can_edit_event($autosave_post_id)) {
            $existing_post_id = $autosave_post_id;
        }
        $title = eep_sanitize_text(wp_unslash($_POST['post_title'] ?? ''));
        if ($existing_post_id > 0 && $title === '') {
            $title = $this->recover_existing_event_title_value($existing_post_id, (string) get_the_title($existing_post_id));
        }
        if ($existing_post_id > 0 && trim((string) ($_POST['eep_start'] ?? '')) === '') {
            $recovered_start = $this->recover_existing_event_datetime_value($existing_post_id, 'eep_start');
            if ($recovered_start !== '') {
                $_POST['eep_start'] = $recovered_start;
            }
        }
        if ($existing_post_id > 0 && trim((string) ($_POST['eep_end'] ?? '')) === '') {
            $recovered_end = $this->recover_existing_event_datetime_value($existing_post_id, 'eep_end');
            if ($recovered_end !== '') {
                $_POST['eep_end'] = $recovered_end;
            }
        }
        if ($requires_complete_submission && strlen($title) < 8) {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), sprintf(__('%s name should be at least 8 characters.', 'nexdirectory'), $singular_label), 'error');
        }
        $contact_email_raw = trim((string) wp_unslash($_POST['eep_contact_email'] ?? ''));
        $contact_email = sanitize_email($contact_email_raw);
        if ($contact_email_raw !== '' && !is_email($contact_email)) {
            if ($requires_complete_submission) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Please enter a valid contact email or leave it blank.', 'nexdirectory'), 'error');
            }
            $contact_email = '';
        }
        $_POST['eep_contact_email'] = $contact_email;
        $job_application_email_raw = trim((string) wp_unslash($_POST['eep_job_application_email'] ?? ''));
        $job_application_email = sanitize_email($job_application_email_raw);
        if ($job_application_email_raw !== '' && !is_email($job_application_email)) {
            if ($requires_complete_submission) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Please enter a valid application email or leave it blank.', 'nexdirectory'), 'error');
            }
            $job_application_email = '';
        }
        $_POST['eep_job_application_email'] = $job_application_email;
        $parsed_tags = $this->parse_event_tags_input(wp_unslash($_POST['event_tags'] ?? ''), 8, 40);
        if (is_wp_error($parsed_tags)) {
            if ($requires_complete_submission) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), $parsed_tags->get_error_message(), 'error');
            }
            $parsed_tags = [];
        }

        if (!$is_events_profile) {
            $start_value = trim((string) ($_POST['eep_start'] ?? ''));
            $_POST['eep_recurrence'] = 'none';
            $_POST['eep_recurrence_until'] = '';
            $_POST['eep_sessions'] = '[]';
            $_POST['eep_key_speakers'] = '[]';
            if ($is_job_profile) {
                $job_posted_date = trim((string) ($_POST['eep_job_posted_date'] ?? ''));
                if ($job_posted_date !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $job_posted_date) && strtotime($job_posted_date . ' 00:00:00')) {
                    $_POST['eep_start'] = $job_posted_date . ' 09:00:00';
                } elseif ($start_value === '') {
                    $_POST['eep_start'] = current_time('Y-m-d H:i:s');
                }
                if ($job_posted_date === '' && !empty($_POST['eep_start']) && preg_match('/^\d{4}-\d{2}-\d{2}/', (string) $_POST['eep_start'], $posted_match)) {
                    $_POST['eep_job_posted_date'] = (string) ($posted_match[0] ?? '');
                }
                $job_closing = trim((string) ($_POST['eep_job_closing_date'] ?? ''));
                if ($job_closing !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $job_closing)) {
                    $_POST['eep_end'] = $job_closing . ' 23:59:00';
                } elseif (trim((string) ($_POST['eep_end'] ?? '')) === '') {
                    $_POST['eep_end'] = '';
                }
            } elseif ($is_marketplace_profile) {
                if ($start_value === '') {
                    $_POST['eep_start'] = current_time('Y-m-d H:i:s');
                }
                $market_expires_on = trim((string) ($_POST['eep_market_expires_on'] ?? ''));
                if ($market_expires_on !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $market_expires_on)) {
                    $_POST['eep_end'] = $market_expires_on . ' 23:59:00';
                } else {
                    $_POST['eep_end'] = '';
                }
            } else {
                if ($start_value === '') {
                    $_POST['eep_start'] = current_time('Y-m-d H:i:s');
                }
                $_POST['eep_end'] = '';
            }
        }

        if ($requires_complete_submission) {
            $time_check = $this->validate_event_times($_POST['eep_start'], $_POST['eep_end'], true);
            if (is_wp_error($time_check)) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), $time_check->get_error_message(), 'error');
            }
            if ($is_job_profile) {
                if (trim((string) ($_POST['eep_company_name'] ?? '')) === '') {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Company name is required for job listings.', 'nexdirectory'), 'error');
                }
                $job_posted = trim((string) ($_POST['eep_job_posted_date'] ?? ''));
                if ($job_posted !== '' && (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $job_posted) || !strtotime($job_posted . ' 00:00:00'))) {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Posted date is invalid.', 'nexdirectory'), 'error');
                }
                if (trim((string) ($_POST['eep_job_type'] ?? '')) === '') {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Employment type is required for job listings.', 'nexdirectory'), 'error');
                }
                if (trim((string) ($_POST['eep_job_department'] ?? '')) === '') {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Department is required for job listings.', 'nexdirectory'), 'error');
                }
                if (!in_array((string) ($_POST['eep_job_work_mode'] ?? ''), ['on-site', 'hybrid', 'remote'], true)) {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Work setup is required for job listings.', 'nexdirectory'), 'error');
                }
                if (trim((string) ($_POST['eep_job_compensation_type'] ?? '')) === '') {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Compensation type is required for job listings.', 'nexdirectory'), 'error');
                }
                if (trim((string) ($_POST['eep_job_salary'] ?? '')) === '') {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Salary range is required for job listings.', 'nexdirectory'), 'error');
                }
                if (trim((string) ($_POST['eep_job_apply_url'] ?? '')) === '' && trim((string) ($_POST['eep_job_application_email'] ?? '')) === '') {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Provide at least one application method (URL or email).', 'nexdirectory'), 'error');
                }
                $job_closing = trim((string) ($_POST['eep_job_closing_date'] ?? ''));
                if ($job_closing === '') {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Closing date is required for job listings.', 'nexdirectory'), 'error');
                }
                if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $job_closing) || !strtotime($job_closing . ' 00:00:00')) {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Closing date is invalid.', 'nexdirectory'), 'error');
                }
            } elseif ($is_directory_profile) {
                if (trim((string) ($_POST['eep_company_name'] ?? '')) === '') {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Business name is required for directory listings.', 'nexdirectory'), 'error');
                }
                if (!in_array((string) ($_POST['eep_directory_business_type'] ?? ''), ['independent-business', 'agency', 'private-owner', 'nonprofit', 'multi-location', 'franchise'], true)) {
                    $_POST['eep_directory_business_type'] = 'independent-business';
                }
                if (!in_array((string) ($_POST['eep_directory_listing_status'] ?? ''), ['active', 'temporarily-closed', 'permanently-closed'], true)) {
                    $_POST['eep_directory_listing_status'] = 'active';
                }
                $has_contact_method = (
                    trim((string) ($_POST['eep_contact_phone'] ?? '')) !== ''
                    || trim((string) ($_POST['eep_contact_email'] ?? '')) !== ''
                    || trim((string) ($_POST['eep_external_url'] ?? '')) !== ''
                    || trim((string) ($_POST['eep_directory_appointment_url'] ?? '')) !== ''
                    || trim((string) ($_POST['eep_directory_whatsapp_url'] ?? '')) !== ''
                );
                if (!$has_contact_method) {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Add at least one owner contact method (email, phone, website, booking URL, or WhatsApp).', 'nexdirectory'), 'error');
                }
            } elseif ($is_marketplace_profile) {
                if (trim((string) ($_POST['eep_market_condition'] ?? '')) === '') {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Condition is required for marketplace listings.', 'nexdirectory'), 'error');
                }
                $market_price_type = sanitize_key((string) ($_POST['eep_market_price_type'] ?? 'fixed'));
                if (!in_array($market_price_type, ['fixed', 'negotiable', 'auction', 'free'], true)) {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Price type is invalid for marketplace listings.', 'nexdirectory'), 'error');
                }
                if ($market_price_type !== 'free' && trim((string) ($_POST['eep_price'] ?? '')) === '') {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Asking price is required unless this listing is marked as free.', 'nexdirectory'), 'error');
                }
                if ($market_price_type === 'free' && trim((string) ($_POST['eep_price'] ?? '')) === '') {
                    $_POST['eep_price'] = __('Free', 'nexdirectory');
                }
                $market_listing_status = sanitize_key((string) ($_POST['eep_market_listing_status'] ?? 'active'));
                if (!in_array($market_listing_status, ['active', 'reserved', 'sold', 'expired'], true)) {
                    $_POST['eep_market_listing_status'] = 'active';
                }
                $market_seller_type = sanitize_key((string) ($_POST['eep_market_seller_type'] ?? 'private'));
                if (!in_array($market_seller_type, ['private', 'business'], true)) {
                    $_POST['eep_market_seller_type'] = 'private';
                }
                $market_expires_on = trim((string) ($_POST['eep_market_expires_on'] ?? ''));
                if ($market_expires_on !== '' && (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $market_expires_on) || !strtotime($market_expires_on . ' 00:00:00'))) {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Expiry date is invalid for marketplace listings.', 'nexdirectory'), 'error');
                }
                $market_delivery_selected = array_filter(array_map('sanitize_key', preg_split('/[\s,]+/', (string) ($_POST['eep_market_delivery_methods'] ?? '')) ?: []));
                if (empty($market_delivery_selected)) {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Select at least one delivery method for marketplace listings.', 'nexdirectory'), 'error');
                }
                $has_contact_method = (
                    trim((string) ($_POST['eep_contact_phone'] ?? '')) !== ''
                    || trim((string) ($_POST['eep_contact_email'] ?? '')) !== ''
                    || trim((string) ($_POST['eep_external_url'] ?? '')) !== ''
                );
                if (!$has_contact_method) {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Add at least one seller contact method (email, phone, or external listing URL).', 'nexdirectory'), 'error');
                }
            }
        }
        if ($_POST['eep_timezone'] === '') {
            $site_tz = $this->sanitize_timezone_value(wp_timezone_string());
            if ($site_tz !== '') {
                $_POST['eep_timezone'] = $site_tz;
            }
        }
        $address_value = sanitize_text_field((string) wp_unslash($_POST['eep_address'] ?? ''));
        $city_value = sanitize_text_field((string) wp_unslash($_POST['eep_city'] ?? ''));
        $venue_value = sanitize_text_field((string) wp_unslash($_POST['eep_venue'] ?? ''));
        if ($requires_complete_submission && $address_value === '' && ($is_events_profile || $is_directory_profile)) {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Address is required.', 'nexdirectory'), 'error');
        }
        if ($city_value === '' && $address_value !== '') {
            $parts = array_filter(array_map('trim', explode(',', $address_value)));
            $city_value = !empty($parts) ? (string) end($parts) : $address_value;
            $_POST['eep_city'] = $city_value;
        }
        $_POST['eep_venue'] = $venue_value;
        $this->maybe_fill_request_coordinates_from_location_fields();

        $content = wp_kses_post(wp_unslash($_POST['post_content'] ?? ''));
        if ($existing_post_id > 0 && !array_key_exists('post_content', $_POST)) {
            $content = (string) get_post_field('post_content', $existing_post_id);
        }
        if ($existing_post_id > 0 && trim((string) $content) === '') {
            $existing_content = (string) get_post_field('post_content', $existing_post_id);
            if (trim($existing_content) !== '') {
                $content = $existing_content;
            }
        }
        $excerpt = eep_sanitize_textarea(wp_unslash($_POST['post_excerpt'] ?? ''));
        if ($existing_post_id > 0 && !array_key_exists('post_excerpt', $_POST)) {
            $excerpt = (string) get_post_field('post_excerpt', $existing_post_id);
        }
        if ($existing_post_id > 0 && trim((string) $excerpt) === '') {
            $existing_excerpt = (string) get_post_field('post_excerpt', $existing_post_id);
            if (trim($existing_excerpt) !== '') {
                $excerpt = $existing_excerpt;
            }
        }
        $excerpt_max_chars = max(80, min(1000, (int) get_option('eep_excerpt_max_chars', 220)));
        $excerpt_length = function_exists('mb_strlen') ? mb_strlen($excerpt) : strlen($excerpt);
        if ($excerpt_length > $excerpt_max_chars) {
            $this->redirect_with_message(
                wp_get_referer() ?: home_url('/'),
                sprintf(__('Short excerpt must be %d characters or fewer.', 'nexdirectory'), $excerpt_max_chars),
                'error'
            );
        }
        if ($excerpt === '') {
            $excerpt = wp_trim_words(eep_strip_all_tags($content), 26);
        }
        $custom_fields_enabled = (string) get_option('eep_enable_custom_submission_fields', '0') === '1';
        $custom_form_fields = [];
        $custom_field_values = [];
        if ($custom_fields_enabled) {
            $custom_payload = wp_unslash($_POST['eep_custom_fields_builder_json'] ?? '[]');
            $custom_parsed = $this->parse_custom_field_builder_payload(is_string($custom_payload) ? $custom_payload : '[]');
            $custom_form_fields = is_array($custom_parsed['fields'] ?? null) ? (array) $custom_parsed['fields'] : [];
            $custom_field_values = is_array($custom_parsed['values'] ?? null) ? (array) $custom_parsed['values'] : [];
            if ($requires_complete_submission) {
                $custom_required_check = $this->validate_custom_form_fields_required($custom_form_fields, $custom_field_values);
                if (is_wp_error($custom_required_check)) {
                    $this->redirect_with_message(wp_get_referer() ?: home_url('/'), $custom_required_check->get_error_message(), 'error');
                }
            }
        }

        $duplicate_candidate_id = 0;
        if ((string) get_option('eep_enable_duplicate_detection', '1') === '1') {
            $duplicate_candidate_id = $this->find_duplicate_event_candidate(
                $title,
                $_POST['eep_start'],
                $city_value,
                $address_value,
                $venue_value,
                $is_edit_mode ? $edit_post_id : 0
            );
        }

        $post_args = [
            'post_type' => self::POST_TYPE,
            'post_status' => $post_status,
            'post_author' => get_current_user_id(),
            'post_title' => $title,
            'post_content' => $content,
            'post_excerpt' => $excerpt,
        ];
        if ($is_edit_mode) {
            $post_args['ID'] = $edit_post_id;
            unset($post_args['post_author']);
            $post_id = wp_update_post($post_args, true);
        } elseif ($autosave_post_id > 0 && $this->user_can_edit_event($autosave_post_id)) {
            $post_args['ID'] = $autosave_post_id;
            unset($post_args['post_author']);
            $post_id = wp_update_post($post_args, true);
        } else {
            $post_id = wp_insert_post($post_args, true);
        }

        if (is_wp_error($post_id)) {
            wp_die(esc_html($post_id->get_error_message()));
        }
        $this->ensure_event_owner_team_member((int) $post_id);

        $upload_limits = $this->get_upload_limits();
        $cover_upload_urls = $this->collect_uploaded_gallery_image_urls_from_request(
            'eep_cover_file',
            1,
            (int) ($upload_limits['cover_max_bytes'] ?? 512000)
        );
        $submitted_cover_url = esc_url_raw((string) wp_unslash($_POST['eep_cover_image_url'] ?? ''));
        if ($submitted_cover_url === '' && !empty($cover_upload_urls[0])) {
            $_POST['eep_cover_image_url'] = (string) $cover_upload_urls[0];
        }
        $_POST['eep_cover_image_url'] = $this->can_current_user_use_media_url((string) ($_POST['eep_cover_image_url'] ?? ''), get_current_user_id())
            ? esc_url_raw((string) ($_POST['eep_cover_image_url'] ?? ''))
            : '';

        $submitted_gallery_urls = $this->parse_multiline_urls((string) wp_unslash($_POST['eep_gallery_urls'] ?? ''));
        $uploaded_gallery_urls = $this->collect_uploaded_gallery_image_urls_from_request('eep_gallery_files');
        if (!empty($uploaded_gallery_urls)) {
            $_POST['eep_gallery_urls'] = implode("\n", array_values(array_unique(array_merge($submitted_gallery_urls, $uploaded_gallery_urls))));
        }
        $_POST['eep_gallery_urls'] = $this->filter_gallery_urls_text_for_user((string) ($_POST['eep_gallery_urls'] ?? ''), get_current_user_id());
        $normalized_media = $this->normalize_cover_and_gallery_meta_urls(
            (string) ($_POST['eep_cover_image_url'] ?? ''),
            (string) ($_POST['eep_gallery_urls'] ?? '')
        );
        $_POST['eep_cover_image_url'] = (string) ($normalized_media['cover'] ?? '');
        $_POST['eep_gallery_urls'] = implode("\n", (array) ($normalized_media['gallery'] ?? []));
        if ($is_events_profile && $this->request_contains_event_webapp_fields()) {
            $this->normalize_event_webapp_request_data($current_user_id);
        }

        $is_existing_post_update = $existing_post_id > 0;
        foreach ($this->get_event_meta_fields() as $key => $type) {
            if (!array_key_exists($key, $_POST)) {
                if ($is_existing_post_update) {
                    continue;
                }
                $value = '';
            } else {
                $value = wp_unslash($_POST[$key]);
            }
            if ($this->should_skip_sparse_meta_update($post_id, $key, $value, $is_existing_post_update)) {
                continue;
            }
            update_post_meta($post_id, $key, $this->sanitize_meta_value($key, $value));
        }
        $this->persist_listing_profile_for_post($post_id, $profile_key);
        $resolved_official_value = $this->apply_official_flag_with_admin_gate($post_id, $requested_official_value, get_current_user_id());
        $_POST['eep_official_event'] = $resolved_official_value;

        $video_urls = $this->build_video_urls_from_fields(
            wp_unslash($_POST['eep_event_stream_url'] ?? ''),
            (string) $_POST['eep_youtube_video_id_1'],
            (string) $_POST['eep_youtube_video_id_2'],
            wp_unslash($_POST['eep_video_urls'] ?? '')
        );
        $this->update_media_meta(
            $post_id,
            $video_urls,
            (string) ($_POST['eep_gallery_urls'] ?? '')
        );
        if ($custom_fields_enabled) {
            if (!empty($custom_form_fields)) {
                update_post_meta($post_id, 'eep_custom_fields_schema', wp_json_encode($custom_form_fields));
            } else {
                delete_post_meta($post_id, 'eep_custom_fields_schema');
            }
            $this->update_post_custom_field_values($post_id, $custom_field_values, $custom_form_fields);
        }

        $sessions = $this->parse_sessions($_POST['eep_sessions']);
        update_post_meta($post_id, 'eep_sessions', wp_json_encode($sessions));
        $key_speakers = $this->parse_key_speakers($_POST['eep_key_speakers']);
        update_post_meta($post_id, 'eep_key_speakers', wp_json_encode($key_speakers));
        if ($_POST['eep_recurrence'] === 'custom' && !empty($sessions)) {
            $last_date = '';
            foreach ($sessions as $session) {
                $candidate = (string) ($session['end'] ?? $session['start'] ?? '');
                if ($candidate !== '' && ($last_date === '' || $candidate > $last_date)) {
                    $last_date = $candidate;
                }
            }
            if ($last_date !== '') {
                update_post_meta($post_id, 'eep_recurrence_until', substr($last_date, 0, 10));
            }
        }

        if (!empty($_POST['event_type']) && intval($_POST['event_type']) > 0) {
            wp_set_post_terms($post_id, [intval($_POST['event_type'])], self::TAX_TYPE, false);
        }

        if ($is_job_profile) {
            $job_type_terms = [];
            $job_department_label = trim((string) ($_POST['eep_job_department'] ?? ''));
            $job_type_label = trim((string) ($_POST['eep_job_type'] ?? ''));
            foreach ([$job_department_label, $job_type_label] as $candidate_term) {
                if ($candidate_term === '') {
                    continue;
                }
                $candidate_term_id = $this->get_or_create_term_id(self::TAX_TYPE, $candidate_term);
                if ($candidate_term_id > 0) {
                    $job_type_terms[] = (int) $candidate_term_id;
                }
            }
            if (!empty($job_type_terms)) {
                wp_set_post_terms($post_id, array_values(array_unique($job_type_terms)), self::TAX_TYPE, false);
            }
            $work_mode = sanitize_key((string) ($_POST['eep_job_work_mode'] ?? ''));
            $work_mode_label = '';
            if ($work_mode === 'remote') {
                $work_mode_label = __('Remote', 'nexdirectory');
            } elseif ($work_mode === 'hybrid') {
                $work_mode_label = __('Hybrid', 'nexdirectory');
            } elseif ($work_mode === 'on-site') {
                $work_mode_label = __('On-site', 'nexdirectory');
            }
            if ($work_mode_label !== '') {
                $work_mode_term_id = $this->get_or_create_term_id(self::TAX_MODE, $work_mode_label);
                if ($work_mode_term_id > 0) {
                    wp_set_post_terms($post_id, [$work_mode_term_id], self::TAX_MODE, false);
                }
            }
            wp_set_post_terms($post_id, [], self::TAX_PRICE, false);
        } elseif ($is_marketplace_profile) {
            $delivery_methods = array_filter(array_map('sanitize_key', preg_split('/[\s,]+/', (string) ($_POST['eep_market_delivery_methods'] ?? '')) ?: []));
            $delivery_methods = array_values(array_unique(array_filter($delivery_methods, static function ($method) {
                return in_array($method, ['pickup', 'shipping', 'local-delivery', 'digital'], true);
            })));
            $delivery_labels = [
                'pickup' => __('Pickup', 'nexdirectory'),
                'shipping' => __('Shipping', 'nexdirectory'),
                'local-delivery' => __('Local Delivery', 'nexdirectory'),
                'digital' => __('Digital Delivery', 'nexdirectory'),
            ];
            $mode_term_ids = [];
            foreach ($delivery_methods as $delivery_method) {
                $term_name = (string) ($delivery_labels[$delivery_method] ?? '');
                if ($term_name === '') {
                    continue;
                }
                $term_id = $this->get_or_create_term_id(self::TAX_MODE, $term_name);
                if ($term_id > 0) {
                    $mode_term_ids[] = (int) $term_id;
                }
            }
            if (!empty($mode_term_ids)) {
                wp_set_post_terms($post_id, array_values(array_unique($mode_term_ids)), self::TAX_MODE, false);
            } else {
                wp_set_post_terms($post_id, [], self::TAX_MODE, false);
            }

            $market_price_type = sanitize_key((string) ($_POST['eep_market_price_type'] ?? 'fixed'));
            $price_term_map = [
                'free' => __('Free', 'nexdirectory'),
                'fixed' => __('Fixed Price', 'nexdirectory'),
                'negotiable' => __('Negotiable', 'nexdirectory'),
                'auction' => __('Auction', 'nexdirectory'),
            ];
            $price_term_name = (string) ($price_term_map[$market_price_type] ?? $price_term_map['fixed']);
            $price_term_id = $this->get_or_create_term_id(self::TAX_PRICE, $price_term_name);
            if ($price_term_id > 0) {
                wp_set_post_terms($post_id, [$price_term_id], self::TAX_PRICE, false);
            } else {
                wp_set_post_terms($post_id, [], self::TAX_PRICE, false);
            }
        } elseif ($is_directory_profile) {
            $directory_status = sanitize_key((string) ($_POST['eep_directory_listing_status'] ?? 'active'));
            $status_term_map = [
                'active' => __('Active', 'nexdirectory'),
                'temporarily-closed' => __('Temporarily Closed', 'nexdirectory'),
                'permanently-closed' => __('Permanently Closed', 'nexdirectory'),
            ];
            $status_term_name = (string) ($status_term_map[$directory_status] ?? $status_term_map['active']);
            $status_term_id = $this->get_or_create_term_id(self::TAX_MODE, $status_term_name);
            if ($status_term_id > 0) {
                wp_set_post_terms($post_id, [$status_term_id], self::TAX_MODE, false);
            } else {
                wp_set_post_terms($post_id, [], self::TAX_MODE, false);
            }

            wp_set_post_terms($post_id, [], self::TAX_PRICE, false);

            $directory_type = sanitize_key((string) ($_POST['eep_directory_business_type'] ?? 'independent-business'));
            $directory_type_term_map = [
                'independent-business' => __('Independent Business', 'nexdirectory'),
                'agency' => __('Agency', 'nexdirectory'),
                'private-owner' => __('Private Owner', 'nexdirectory'),
                'nonprofit' => __('Nonprofit / Community', 'nexdirectory'),
                'multi-location' => __('Multi-location Brand', 'nexdirectory'),
                'franchise' => __('Franchise', 'nexdirectory'),
            ];
            $directory_type_term_name = (string) ($directory_type_term_map[$directory_type] ?? '');
            if ($directory_type_term_name !== '') {
                $existing_type_ids = wp_get_post_terms($post_id, self::TAX_TYPE, ['fields' => 'ids']);
                if (is_wp_error($existing_type_ids) || !is_array($existing_type_ids)) {
                    $existing_type_ids = [];
                }
                $directory_type_term_id = $this->get_or_create_term_id(self::TAX_TYPE, $directory_type_term_name);
                if ($directory_type_term_id > 0) {
                    $existing_type_ids[] = (int) $directory_type_term_id;
                    wp_set_post_terms($post_id, array_values(array_unique(array_map('absint', $existing_type_ids))), self::TAX_TYPE, false);
                }
            }
        } else {
            $mode_option = sanitize_key((string) wp_unslash($_POST['eep_event_mode_option'] ?? 'in-person'));
            $price_option = sanitize_key((string) wp_unslash($_POST['eep_price_simple'] ?? 'free'));

            if ($is_events_profile) {
                $mode_name = $mode_option === 'video-stream' ? 'Online Stream Event' : 'In-person';
                $price_name = $price_option === 'paid' ? 'Paid' : 'Free';
            } elseif ($is_directory_profile) {
                $mode_name = $mode_option === 'video-stream' ? __('Online Service', 'nexdirectory') : __('On-site Service', 'nexdirectory');
                $price_name = $price_option === 'paid' ? __('Paid Service', 'nexdirectory') : __('Free Service', 'nexdirectory');
            } else {
                $mode_name = $mode_option === 'video-stream' ? __('Online', 'nexdirectory') : __('Standard', 'nexdirectory');
                $price_name = $price_option === 'paid' ? __('Paid', 'nexdirectory') : __('Free', 'nexdirectory');
            }

            $mode_term_id = $this->get_or_create_term_id(self::TAX_MODE, $mode_name);
            if ($mode_term_id > 0) {
                wp_set_post_terms($post_id, [$mode_term_id], self::TAX_MODE, false);
            }

            $price_term_id = $this->get_or_create_term_id(self::TAX_PRICE, $price_name);
            if ($price_term_id > 0) {
                wp_set_post_terms($post_id, [$price_term_id], self::TAX_PRICE, false);
            }
        }

        wp_set_post_terms($post_id, (array) $parsed_tags, self::TAX_TAG, false);
        $this->save_custom_taxonomy_terms_from_request($post_id);
        $missing_optional_sections = $this->get_event_optional_sections_missing($post_id, $profile_key);
        if (!empty($missing_optional_sections)) {
            update_post_meta($post_id, 'eep_missing_optional_sections', wp_json_encode($missing_optional_sections));
        } else {
            delete_post_meta($post_id, 'eep_missing_optional_sections');
        }
        $completion_hint_text = $this->get_event_optional_sections_hint_text($missing_optional_sections);

        if ($post_status === 'pending') {
            $this->notify_admin_submission($post_id);
            $this->notify_author_status($post_id, 'pending', '');
            if ($duplicate_candidate_id > 0) {
                update_post_meta($post_id, 'eep_duplicate_candidate', (string) $duplicate_candidate_id);
            } else {
                delete_post_meta($post_id, 'eep_duplicate_candidate');
            }
            $message = $is_edit_mode
                ? sprintf(__('%s updated and sent for admin verification.', 'nexdirectory'), $singular_label)
                : sprintf(__('%s submitted and sent for admin verification.', 'nexdirectory'), $singular_label);
            $message .= ' ' . __('Your listing will go live once it has been approved by the site admin.', 'nexdirectory');
            if ($requested_official_value === '1' && $resolved_official_value !== '1') {
                $message .= ' ' . __('Official classification was requested and will appear only after admin confirmation.', 'nexdirectory');
            }
            if ($duplicate_candidate_id > 0) {
                $message .= ' ' . __('Potential duplicate detected (same title/date/location). Admin review recommended.', 'nexdirectory');
            }
            if ($completion_hint_text !== '') {
                $message .= ' ' . $completion_hint_text;
            }
            $my_events_url = $this->get_platform_page_url(
                'eep_page_my_events_url',
                $this->get_profile_route_slugs('my_items'),
                $this->get_profile_route_default_url('my_items')
            );
            $this->redirect_with_message(
                $my_events_url,
                $message
            );
        }

        if ($intent === 'preview') {
            $preview_url = (string) get_preview_post_link($post_id);
            if ($preview_url === '' && $post_id > 0) {
                $preview_url = eep_add_query_arg([
                    'p' => (int) $post_id,
                    'post_type' => $this::POST_TYPE,
                    'preview' => 'true',
                ], home_url('/'));
            }
            if ($is_ajax_submit) {
                wp_send_json_success([
                    'message' => sprintf(__('%s preview is ready.', 'nexdirectory'), $singular_label),
                    'type' => 'success',
                    'post_id' => (int) $post_id,
                    'edit_event_id' => (int) $post_id,
                    'preview_url' => $preview_url,
                ]);
            }
            $this->safe_redirect_or_fallback($preview_url);
        }

        $this->redirect_with_message(
            wp_get_referer() ?: home_url('/'),
            ($is_edit_mode
                ? sprintf(__('%s changes saved as draft.', 'nexdirectory'), $singular_label)
                : sprintf(__('%s saved as draft.', 'nexdirectory'), $singular_label))
            . ($completion_hint_text !== '' ? (' ' . $completion_hint_text) : '')
        );
    }


    public function handle_autosave_event() {
        if (!is_user_logged_in()) {
            wp_send_json_error(['message' => __('Login required.', 'nexdirectory')], 403);
        }
        if (!isset($_POST['nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['nonce'])), 'eep_autosave_event_nonce')) {
            wp_send_json_error(['message' => __('Invalid nonce.', 'nexdirectory')], 403);
        }

        $autosave_post_id = isset($_POST['eep_autosave_post_id']) ? absint($_POST['eep_autosave_post_id']) : 0;
        $edit_post_id = isset($_POST['eep_edit_post_id']) ? absint($_POST['eep_edit_post_id']) : 0;
        $current_user_id = (int) get_current_user_id();
        $can_edit_existing = (
            ($edit_post_id > 0 && $this->user_can_edit_event($edit_post_id, $current_user_id))
            || ($autosave_post_id > 0 && $this->user_can_edit_event($autosave_post_id, $current_user_id))
        );
        if (!$this->user_can_submit_events($current_user_id) && !$can_edit_existing) {
            wp_send_json_error(['message' => __('You do not have permission to submit events.', 'nexdirectory')], 403);
        }
        $profile_source_post_id = $edit_post_id > 0 ? $edit_post_id : $autosave_post_id;
        $profile_key = $this->resolve_profile_key_for_post($profile_source_post_id);
        $is_events_profile = $profile_key === 'events';
        $is_edit_autosave = $edit_post_id > 0 && $this->user_can_edit_event($edit_post_id, $current_user_id);
        $has_existing_autosave_target = $autosave_post_id > 0 && $this->user_can_edit_event($autosave_post_id, $current_user_id);
        if (!$is_edit_autosave && !$has_existing_autosave_target && !$this->can_create_more_events($current_user_id, 1)) {
            $limit_status = (array) $this->get_demo_event_limit_status();
            $limit = max(1, (int) ($limit_status['limit'] ?? 3));
            $used = max(0, (int) ($limit_status['used'] ?? $limit));
            wp_send_json_error([
                'message' => sprintf(
                    __('Demo mode includes %1$d free events. You currently have %2$d. Upgrade or activate your license to add more.', 'nexdirectory'),
                    $limit,
                    $used
                ),
                'code' => 'demo_event_limit_reached',
                'limit' => $limit,
                'used' => $used,
            ], 403);
        }
        $title = eep_sanitize_text(wp_unslash($_POST['post_title'] ?? ''));
        if ($title === '' && $is_edit_autosave) {
            $title = $this->recover_existing_event_title_value($edit_post_id, (string) get_the_title($edit_post_id));
        }
        if ($title === '' && $autosave_post_id > 0 && $this->user_can_edit_event($autosave_post_id, $current_user_id)) {
            $title = $this->recover_existing_event_title_value($autosave_post_id, (string) get_the_title($autosave_post_id));
        }
        if ($title === '') {
            wp_send_json_error(['message' => __('Missing title.', 'nexdirectory')], 400);
        }
        $content = wp_kses_post(wp_unslash($_POST['post_content'] ?? ''));
        if ($is_edit_autosave && !array_key_exists('post_content', $_POST)) {
            $content = (string) get_post_field('post_content', $edit_post_id);
        }
        if ($is_edit_autosave && trim((string) $content) === '') {
            $existing_content = (string) get_post_field('post_content', $edit_post_id);
            if (trim($existing_content) !== '') {
                $content = $existing_content;
            }
        }
        $excerpt = eep_sanitize_textarea(wp_unslash($_POST['post_excerpt'] ?? ''));
        if ($is_edit_autosave && !array_key_exists('post_excerpt', $_POST)) {
            $excerpt = (string) get_post_field('post_excerpt', $edit_post_id);
        }
        if ($is_edit_autosave && trim((string) $excerpt) === '') {
            $existing_excerpt = (string) get_post_field('post_excerpt', $edit_post_id);
            if (trim($existing_excerpt) !== '') {
                $excerpt = $existing_excerpt;
            }
        }
        $excerpt_max_chars = max(80, min(1000, (int) get_option('eep_excerpt_max_chars', 220)));
        $excerpt_length = function_exists('mb_strlen') ? mb_strlen($excerpt) : strlen($excerpt);
        if ($excerpt !== '' && $excerpt_length > $excerpt_max_chars) {
            $excerpt = function_exists('mb_substr') ? mb_substr($excerpt, 0, $excerpt_max_chars) : substr($excerpt, 0, $excerpt_max_chars);
        }
        if ($excerpt === '') {
            $excerpt = wp_trim_words(eep_strip_all_tags($content), 26);
        }

        $post_data = [
            'post_type' => self::POST_TYPE,
            'post_status' => 'draft',
            'post_author' => get_current_user_id(),
            'post_title' => $title,
            'post_content' => $content,
            'post_excerpt' => $excerpt,
        ];

        if ($is_edit_autosave) {
            $post_data['ID'] = $edit_post_id;
            unset($post_data['post_status'], $post_data['post_author']);
            $post_id = wp_update_post($post_data, true);
        } elseif ($autosave_post_id > 0 && $this->user_can_edit_event($autosave_post_id)) {
            $post_data['ID'] = $autosave_post_id;
            unset($post_data['post_author']);
            $post_id = wp_update_post($post_data, true);
        } else {
            $post_id = wp_insert_post($post_data, true);
        }

        if (is_wp_error($post_id) || !$post_id) {
            wp_send_json_error(['message' => is_wp_error($post_id) ? $post_id->get_error_message() : __('Autosave failed.', 'nexdirectory')], 500);
        }
        $this->ensure_event_owner_team_member((int) $post_id);

        $this->hydrate_event_datetime_fields_from_request();
        $_POST['eep_official_event'] = $this->normalize_official_event_value(wp_unslash($_POST['eep_official_event'] ?? '0'));
        $requested_official_value = (string) $_POST['eep_official_event'];
        $_POST['eep_lock_pin'] = (string) (wp_unslash($_POST['eep_lock_pin'] ?? '0')) === '1' ? '1' : '0';
        $_POST['eep_price_simple'] = sanitize_key((string) wp_unslash($_POST['eep_price_simple'] ?? 'free'));
        $_POST['eep_event_stream_url'] = esc_url_raw((string) wp_unslash($_POST['eep_event_stream_url'] ?? ''));
        $_POST['eep_meta_title'] = $this->sanitize_event_seo_title(wp_unslash($_POST['eep_meta_title'] ?? ''));
        $_POST['eep_meta_description'] = $this->sanitize_event_seo_description(wp_unslash($_POST['eep_meta_description'] ?? ''));
        if ((string) get_option('eep_enable_event_seo_fields', '1') !== '1') {
            unset($_POST['eep_meta_title'], $_POST['eep_meta_description']);
        }
        $_POST['eep_youtube_video_id_1'] = $this->normalize_submitted_youtube_id(wp_unslash($_POST['eep_youtube_video_id_1'] ?? ''));
        $_POST['eep_youtube_video_id_2'] = $this->normalize_submitted_youtube_id(wp_unslash($_POST['eep_youtube_video_id_2'] ?? ''));
        $_POST['eep_listing_profile'] = $profile_key;
        $this->sync_event_price_with_ticket_request($is_events_profile);
        if ($is_events_profile && $this->request_contains_event_webapp_fields()) {
            $this->normalize_event_webapp_request_data($current_user_id);
        }

        $is_existing_autosave_update = $is_edit_autosave || ($autosave_post_id > 0 && $this->user_can_edit_event($autosave_post_id));
        foreach ($this->get_event_meta_fields() as $key => $type) {
            if (!array_key_exists($key, $_POST)) {
                if ($is_existing_autosave_update) {
                    continue;
                }
                $value = '';
            } else {
                $value = wp_unslash($_POST[$key]);
            }
            if ($this->should_skip_sparse_meta_update($post_id, $key, $value, $is_existing_autosave_update)) {
                continue;
            }
            update_post_meta($post_id, $key, $this->sanitize_meta_value($key, $value));
        }
        $this->persist_listing_profile_for_post($post_id, $profile_key);
        $resolved_official_value = $this->apply_official_flag_with_admin_gate($post_id, $requested_official_value, get_current_user_id());
        $_POST['eep_official_event'] = $resolved_official_value;

        $video_urls = $this->build_video_urls_from_fields(
            wp_unslash($_POST['eep_event_stream_url'] ?? ''),
            (string) $_POST['eep_youtube_video_id_1'],
            (string) $_POST['eep_youtube_video_id_2'],
            wp_unslash($_POST['eep_video_urls'] ?? '')
        );
        $this->update_media_meta(
            $post_id,
            $video_urls,
            wp_unslash($_POST['eep_gallery_urls'] ?? '')
        );
        if ((string) get_option('eep_enable_custom_submission_fields', '0') === '1' && isset($_POST['eep_custom_fields_builder_json'])) {
            $custom_payload = wp_unslash($_POST['eep_custom_fields_builder_json'] ?? '[]');
            $custom_parsed = $this->parse_custom_field_builder_payload(is_string($custom_payload) ? $custom_payload : '[]');
            $custom_form_fields = is_array($custom_parsed['fields'] ?? null) ? (array) $custom_parsed['fields'] : [];
            $custom_field_values = is_array($custom_parsed['values'] ?? null) ? (array) $custom_parsed['values'] : [];
            if (!empty($custom_form_fields)) {
                update_post_meta($post_id, 'eep_custom_fields_schema', wp_json_encode($custom_form_fields));
            } else {
                delete_post_meta($post_id, 'eep_custom_fields_schema');
            }
            $this->update_post_custom_field_values($post_id, $custom_field_values, $custom_form_fields);
        }
        $this->save_custom_taxonomy_terms_from_request($post_id);
        $raw_sessions_input = wp_unslash($_POST['eep_sessions'] ?? '[]');
        $raw_key_speakers_input = wp_unslash($_POST['eep_key_speakers'] ?? '[]');
        $has_existing_sessions = trim((string) get_post_meta($post_id, 'eep_sessions', true)) !== '';
        $has_existing_key_speakers = trim((string) get_post_meta($post_id, 'eep_key_speakers', true)) !== '';
        if (!$is_existing_autosave_update || !$this->is_empty_json_collection_value($raw_sessions_input) || !$has_existing_sessions) {
            update_post_meta($post_id, 'eep_sessions', wp_json_encode($this->parse_sessions($raw_sessions_input)));
        }
        if (!$is_existing_autosave_update || !$this->is_empty_json_collection_value($raw_key_speakers_input) || !$has_existing_key_speakers) {
            update_post_meta($post_id, 'eep_key_speakers', wp_json_encode($this->parse_key_speakers($raw_key_speakers_input)));
        }

        wp_send_json_success([
            'message' => __('Draft autosaved.', 'nexdirectory'),
            'post_id' => $post_id,
        ]);
    }


    public function render_my_events($atts = []) {
        $atts = shortcode_atts([
            'posts_per_page' => 30,
        ], (array) $atts, 'eep_my_events');
        $ui_labels = $this->get_site_profile_ui_labels();
        $my_items_title = sanitize_text_field((string) ($ui_labels['my_items_title'] ?? __('My Listings', 'nexdirectory')));

        return $this->render_account_app_template([
            'initial_tab' => 'listings',
            'initial_listings_subtab' => 'manage',
            'posts_per_page' => max(1, (int) $atts['posts_per_page']),
            'login_message' => sprintf(__('Please log in to access %s.', 'nexdirectory'), $my_items_title),
        ]);
    }


    public function render_boost_posts($atts = []) {
        $atts = shortcode_atts([
            'posts_per_page' => 60,
            'history_limit' => 120,
        ], (array) $atts, 'eep_boost_posts');
        return $this->render_account_app_template([
            'initial_tab' => 'listings',
            'initial_listings_subtab' => 'boosts',
            'posts_per_page' => max(1, min(250, (int) $atts['posts_per_page'])),
            'history_limit' => max(1, min(400, (int) $atts['history_limit'])),
            'login_message' => __('Please log in to access Boost Post.', 'nexdirectory'),
        ]);
    }


    private function get_rsvp_rows_for_user($user_id, $limit = 200) {
        $user_id = absint((int) $user_id);
        $limit = max(1, min(1000, (int) $limit));
        if ($user_id <= 0) {
            return [];
        }

        global $wpdb;
        $table = $this->get_rsvp_table_name();
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT post_id, status, created_at, updated_at FROM {$table} WHERE user_id = %d ORDER BY updated_at DESC LIMIT %d",
            $user_id,
            $limit
        ), ARRAY_A);
        if (!is_array($rows) || empty($rows)) {
            return [];
        }

        $prepared_rows = [];
        $post_ids = [];
        foreach ($rows as $row) {
            $post_id = absint((int) ($row['post_id'] ?? 0));
            if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
                continue;
            }

            $status = sanitize_key((string) ($row['status'] ?? ''));
            if (!in_array($status, ['yes', 'waitlist'], true)) {
                continue;
            }

            $post_status = (string) get_post_status($post_id);
            if (!in_array($post_status, ['publish', 'pending', 'draft'], true)) {
                continue;
            }

            $prepared_rows[] = [
                'post_id' => $post_id,
                'status' => $status,
                'post_status' => $post_status,
                'updated_at' => (string) ($row['updated_at'] ?? ''),
            ];
            $post_ids[] = $post_id;
        }
        if (empty($prepared_rows)) {
            return [];
        }

        $ticket_order_map = $this->get_latest_ticket_orders_by_post_for_user($user_id, $post_ids);
        $out = [];
        foreach ($prepared_rows as $row) {
            $post_id = absint((int) ($row['post_id'] ?? 0));
            $status = sanitize_key((string) ($row['status'] ?? ''));
            $post_status = (string) ($row['post_status'] ?? 'publish');
            $start_raw = (string) get_post_meta($post_id, 'eep_start', true);
            $end_raw = (string) get_post_meta($post_id, 'eep_end', true);
            $start_ts = strtotime($start_raw);
            $end_ts = $end_raw !== '' ? strtotime($end_raw) : false;
            $updated_ts = strtotime((string) ($row['updated_at'] ?? ''));
            $address = sanitize_text_field((string) get_post_meta($post_id, 'eep_address', true));
            $city = sanitize_text_field((string) get_post_meta($post_id, 'eep_city', true));
            $country = sanitize_text_field((string) get_post_meta($post_id, 'eep_country', true));
            $location = trim((string) preg_replace('/\s+/u', ' ', implode(', ', array_filter([$address, $city, $country], static function ($v) {
                return trim((string) $v) !== '';
            }))));
            $map_link = $location !== '' ? esc_url_raw('https://maps.google.com/?q=' . rawurlencode($location)) : '';

            $session_rows = [];
            $session_day_labels = [];
            $sessions_raw = $this->parse_sessions((string) get_post_meta($post_id, 'eep_sessions', true));
            if (is_array($sessions_raw)) {
                foreach (array_values(array_slice($sessions_raw, 0, 20)) as $session_index => $session_row) {
                    if (!is_array($session_row)) {
                        continue;
                    }
                    $session_title = sanitize_text_field((string) ($session_row['title'] ?? ''));
                    $session_start_raw = (string) ($session_row['start'] ?? '');
                    $session_end_raw = (string) ($session_row['end'] ?? '');
                    $session_start_ts = $session_start_raw !== '' ? strtotime($session_start_raw) : false;
                    $session_end_ts = $session_end_raw !== '' ? strtotime($session_end_raw) : false;
                    $session_day_label = sanitize_text_field((string) ($session_row['day'] ?? ''));
                    if ($session_day_label === '' && $session_start_ts) {
                        $session_day_label = wp_date(get_option('date_format'), (int) $session_start_ts);
                    }
                    if ($session_day_label !== '') {
                        $session_day_labels[] = $session_day_label;
                    }
                    $time_bits = [];
                    if ($session_start_ts) {
                        $time_bits[] = $this->event_datetime_has_time($session_start_raw)
                            ? wp_date(get_option('time_format'), (int) $session_start_ts)
                            : wp_date(get_option('date_format'), (int) $session_start_ts);
                    }
                    if ($session_end_ts) {
                        $time_bits[] = $this->event_datetime_has_time($session_end_raw)
                            ? wp_date(get_option('time_format'), (int) $session_end_ts)
                            : wp_date(get_option('date_format'), (int) $session_end_ts);
                    }
                    if ($session_start_ts && $session_end_ts && wp_date('Y-m-d', (int) $session_start_ts) !== wp_date('Y-m-d', (int) $session_end_ts)) {
                        $time_bits = [
                            $this->format_event_datetime_for_display($session_start_raw),
                            $this->format_event_datetime_for_display($session_end_raw),
                        ];
                    }
                    if ($session_title === '' && empty($time_bits) && trim((string) ($session_row['speaker'] ?? '')) === '' && trim((string) ($session_row['location'] ?? '')) === '') {
                        continue;
                    }
                    $session_key = sanitize_key((string) ($session_row['session_key'] ?? ''));
                    if ($session_key === '') {
                        $session_key = sanitize_key('s' . substr(md5((string) $post_id . '|' . (string) $session_index . '|' . (string) $session_title . '|' . (string) $session_start_raw . '|' . (string) $session_end_raw), 0, 24));
                    }
                    $session_rows[] = [
                        'session_uid' => sanitize_key('e' . (string) $post_id . '-' . $session_key),
                        'title' => $session_title !== '' ? $session_title : sprintf(__('Session %d', 'nexdirectory'), $session_index + 1),
                        'time' => implode(' - ', $time_bits),
                        'speaker' => sanitize_text_field((string) ($session_row['speaker'] ?? '')),
                        'location' => sanitize_text_field((string) ($session_row['location'] ?? '')),
                        'day_label' => $session_day_label,
                        'notes' => eep_sanitize_textarea((string) ($session_row['notes'] ?? '')),
                    ];
                }
            }
            $session_day_labels = array_values(array_unique(array_filter(array_map('sanitize_text_field', $session_day_labels))));

            $ticket_order = is_array($ticket_order_map[$post_id] ?? null) ? (array) $ticket_order_map[$post_id] : [];
            $ticket_uid = sanitize_text_field((string) ($ticket_order['ticket_uid'] ?? ''));
            $ticket_currency = strtoupper(sanitize_text_field((string) ($ticket_order['currency'] ?? '')));
            $ticket_total = round(max(0, (float) ($ticket_order['ticket_total'] ?? 0)), 2);
            $ticket_total_label = $ticket_total > 0 && $ticket_currency !== '' ? trim($ticket_currency . ' ' . number_format($ticket_total, 2, '.', '')) : '';
            if ($ticket_total <= 0 && !empty($ticket_order)) {
                $ticket_total_label = __('Free', 'nexdirectory');
            }
            if ($ticket_total_label === '' && $ticket_total > 0) {
                $ticket_total_label = number_format($ticket_total, 2, '.', '');
            }
            $ticket_gateway_label = sanitize_text_field((string) ($ticket_order['gateway_label'] ?? ''));
            $ticket_payment_method = sanitize_text_field((string) ($ticket_order['payment_method'] ?? ''));
            $ticket_qr_image_url = esc_url_raw((string) ($ticket_order['qr_image_url'] ?? ''));
            $ticket_verify_url = $this->get_ticket_order_verification_url($ticket_order);
            $ticket_purchased_label = $this->format_ticket_purchase_label((string) ($ticket_order['purchased_at'] ?? ''));
            $ticket_payment_status = sanitize_key((string) ($ticket_order['payment_status'] ?? ''));
            $ticket_payment_status_map = [
                'confirmed' => __('Confirmed', 'nexdirectory'),
                'waitlist' => __('Waitlist', 'nexdirectory'),
                'refunded' => __('Refunded', 'nexdirectory'),
                'failed' => __('Failed', 'nexdirectory'),
                'cancelled' => __('Cancelled', 'nexdirectory'),
            ];
            $ticket_payment_status_label = (string) ($ticket_payment_status_map[$ticket_payment_status] ?? ($ticket_payment_status !== '' ? ucwords(str_replace('_', ' ', $ticket_payment_status)) : ''));

            $out[] = [
                'post_id' => $post_id,
                'title' => (string) get_the_title($post_id),
                'url' => get_permalink($post_id),
                'status' => $status,
                'status_label' => $status === 'waitlist' ? __('Waitlist', 'nexdirectory') : __('Confirmed', 'nexdirectory'),
                'event_status' => $post_status,
                'event_status_label' => $this->event_status_badge($post_status),
                'start_label' => $start_ts ? $this->format_event_datetime_for_display($start_raw) : __('Date TBD', 'nexdirectory'),
                'event_date_label' => $this->format_event_date($post_id),
                'end_label' => $end_ts ? $this->format_event_datetime_for_display($end_raw) : '',
                'updated_label' => $updated_ts ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), $updated_ts) : '',
                'location' => $location,
                'map_link' => $map_link,
                'sessions' => $session_rows,
                'session_day_labels' => $session_day_labels,
                'can_cancel' => is_user_logged_in() && (int) get_current_user_id() === $user_id,
                'rsvp_nonce' => wp_create_nonce('eep_rsvp_' . $post_id),
                'has_ticket_order' => !empty($ticket_order),
                'ticket_uid' => $ticket_uid,
                'ticket_total_label' => $ticket_total_label,
                'ticket_gateway_label' => $ticket_gateway_label,
                'ticket_payment_method' => $ticket_payment_method,
                'ticket_purchased_label' => $ticket_purchased_label,
                'ticket_payment_status_label' => $ticket_payment_status_label,
                'ticket_qr_image_url' => $ticket_qr_image_url,
                'ticket_verify_url' => $ticket_verify_url,
            ];
        }

        return $out;
    }


    private function get_event_attendee_rows($post_id) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return [];
        }

        global $wpdb;
        $table = $this->get_rsvp_table_name();
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT user_id, status, created_at, updated_at FROM {$table} WHERE post_id = %d ORDER BY status ASC, updated_at DESC",
            $post_id
        ), ARRAY_A);
        if (!is_array($rows)) {
            $rows = [];
        }

        $user_ids = [];
        foreach ($rows as $row) {
            $user_id = absint((int) ($row['user_id'] ?? 0));
            if ($user_id > 0) {
                $user_ids[] = $user_id;
            }
        }
        $ticket_order_map = $this->get_latest_ticket_orders_by_user_for_post($post_id, $user_ids);

        $out = [];
        foreach ($rows as $row) {
            $user_id = absint((int) ($row['user_id'] ?? 0));
            if ($user_id <= 0) {
                continue;
            }
            $user = get_user_by('id', $user_id);
            if (!$user instanceof WP_User) {
                continue;
            }
            $status = sanitize_key((string) ($row['status'] ?? 'yes'));
            if (!in_array($status, ['yes', 'waitlist'], true)) {
                $status = 'yes';
            }
            $updated_ts = strtotime((string) ($row['updated_at'] ?? ''));
            $communication_opt_in = $this->user_allows_organizer_communications($user_id);

            $ticket_order = is_array($ticket_order_map[$user_id] ?? null) ? (array) $ticket_order_map[$user_id] : [];
            $ticket_uid = sanitize_text_field((string) ($ticket_order['ticket_uid'] ?? ''));
            $ticket_currency = strtoupper(sanitize_text_field((string) ($ticket_order['currency'] ?? '')));
            $ticket_total = round(max(0, (float) ($ticket_order['ticket_total'] ?? 0)), 2);
            $ticket_total_label = $ticket_total > 0 && $ticket_currency !== '' ? trim($ticket_currency . ' ' . number_format($ticket_total, 2, '.', '')) : '';
            if ($ticket_total <= 0 && !empty($ticket_order)) {
                $ticket_total_label = __('Free', 'nexdirectory');
            }
            if ($ticket_total_label === '' && $ticket_total > 0) {
                $ticket_total_label = number_format($ticket_total, 2, '.', '');
            }
            $ticket_gateway_label = sanitize_text_field((string) ($ticket_order['gateway_label'] ?? ''));
            $ticket_payment_method = sanitize_text_field((string) ($ticket_order['payment_method'] ?? ''));
            $ticket_qr_image_url = esc_url_raw((string) ($ticket_order['qr_image_url'] ?? ''));
            $ticket_verify_url = $this->get_ticket_order_verification_url($ticket_order);
            $ticket_purchased_label = $this->format_ticket_purchase_label((string) ($ticket_order['purchased_at'] ?? ''));
            $ticket_payment_status = sanitize_key((string) ($ticket_order['payment_status'] ?? ''));
            $ticket_payment_status_map = [
                'confirmed' => __('Confirmed', 'nexdirectory'),
                'waitlist' => __('Waitlist', 'nexdirectory'),
                'refunded' => __('Refunded', 'nexdirectory'),
                'failed' => __('Failed', 'nexdirectory'),
                'cancelled' => __('Cancelled', 'nexdirectory'),
            ];
            $ticket_payment_status_label = (string) ($ticket_payment_status_map[$ticket_payment_status] ?? ($ticket_payment_status !== '' ? ucwords(str_replace('_', ' ', $ticket_payment_status)) : ''));

            $out[] = [
                'user_id' => $user_id,
                'name' => (string) ($user->display_name ?: $user->user_login),
                'email' => sanitize_email((string) $user->user_email),
                'status' => $status,
                'status_label' => $status === 'waitlist' ? __('Waitlist', 'nexdirectory') : __('Confirmed', 'nexdirectory'),
                'updated_label' => $updated_ts ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), $updated_ts) : '',
                'communication_opt_in' => $communication_opt_in ? '1' : '0',
                'communication_label' => $communication_opt_in
                    ? __('Email Updates: On', 'nexdirectory')
                    : __('Email Updates: Off', 'nexdirectory'),
                'has_ticket_order' => !empty($ticket_order),
                'ticket_uid' => $ticket_uid,
                'ticket_total_label' => $ticket_total_label,
                'ticket_gateway_label' => $ticket_gateway_label,
                'ticket_payment_method' => $ticket_payment_method,
                'ticket_payment_status_label' => $ticket_payment_status_label,
                'ticket_purchased_label' => $ticket_purchased_label,
                'ticket_qr_image_url' => $ticket_qr_image_url,
                'ticket_verify_url' => $ticket_verify_url,
            ];
        }

        return $out;
    }


    public function render_whos_attending($atts = []) {
        $atts = shortcode_atts([
            'event_id' => 0,
            'status' => 'yes',
        ], (array) $atts, 'eep_whos_attending');
        $ui_labels = $this->get_site_profile_ui_labels();
        $profile_key = sanitize_key((string) ($ui_labels['profile_key'] ?? ''));
        $is_events_profile = (string) ($ui_labels['is_events_profile'] ?? '1') === '1';
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Listing', 'nexdirectory')));
        $plural_label = sanitize_text_field((string) ($ui_labels['plural'] ?? __('Listings', 'nexdirectory')));

        $event_id = absint((int) ($atts['event_id'] ?? 0));
        if ($event_id <= 0 && isset($_GET['event_id'])) {
            $event_id = absint((int) wp_unslash($_GET['event_id']));
        }
        if ($event_id <= 0 && isset($_GET['attendees_event'])) {
            $event_id = absint((int) wp_unslash($_GET['attendees_event']));
        }

        $status_filter = sanitize_key((string) ($atts['status'] ?? 'yes'));
        if (isset($_GET['status'])) {
            $status_filter = sanitize_key((string) wp_unslash($_GET['status']));
        }
        if (!in_array($status_filter, ['yes', 'waitlist', 'all'], true)) {
            $status_filter = 'yes';
        }

        $events_url = $this->get_platform_page_url(
            'eep_page_events_url',
            $this->get_profile_route_slugs('events', $profile_key),
            $this->get_profile_route_default_url('events', $profile_key)
        );

        $page_title = $is_events_profile
            ? __('Who\'s Attending', 'nexdirectory')
            : sprintf(__('Saved %s', 'nexdirectory'), $plural_label);
        $page_description = $is_events_profile
            ? __('See who has RSVP\'d for this event.', 'nexdirectory')
            : sprintf(__('See who has saved this %s.', 'nexdirectory'), strtolower((string) $singular_label));
        $state_message = '';
        $rows = [];
        $event_title = '';
        $event_url = '';
        $confirmed_count = 0;
        $waitlist_count = 0;

        if ($event_id <= 0) {
            $state_message = $is_events_profile
                ? __('Select an event to view attendees.', 'nexdirectory')
                : sprintf(__('Select a %s to view followers.', 'nexdirectory'), strtolower((string) $singular_label));
        } elseif (get_post_type($event_id) !== self::POST_TYPE) {
            $state_message = $is_events_profile
                ? __('Invalid event selected.', 'nexdirectory')
                : sprintf(__('Invalid %s selected.', 'nexdirectory'), strtolower((string) $singular_label));
            $event_id = 0;
        } else {
            $event_title = (string) get_the_title($event_id);
            $event_url = (string) get_permalink($event_id);
            $owner_id = (int) get_post_field('post_author', $event_id);
            $can_view_private = is_user_logged_in() && ((int) get_current_user_id() === $owner_id || $this->user_can_moderate_events());
            $post_status = (string) get_post_status($event_id);
            if ($post_status !== 'publish' && !$can_view_private) {
                $state_message = __('This attendee list is not public yet.', 'nexdirectory');
                $event_id = 0;
            } else {
                $raw_rows = $this->get_event_attendee_rows($event_id);
                $confirmed_count = count(array_filter($raw_rows, static function ($row) {
                    return (string) ($row['status'] ?? '') === 'yes';
                }));
                $waitlist_count = count(array_filter($raw_rows, static function ($row) {
                    return (string) ($row['status'] ?? '') === 'waitlist';
                }));

                foreach ($raw_rows as $row) {
                    $row_status = sanitize_key((string) ($row['status'] ?? ''));
                    if ($status_filter !== 'all' && $row_status !== $status_filter) {
                        continue;
                    }
                    $rows[] = [
                        'name' => sanitize_text_field((string) ($row['name'] ?? '')),
                        'status' => $row_status,
                        'status_label' => sanitize_text_field((string) ($row['status_label'] ?? '')),
                        'updated_label' => sanitize_text_field((string) ($row['updated_label'] ?? '')),
                    ];
                }

                if (empty($rows)) {
                    $state_message = $is_events_profile
                        ? __('No RSVPs yet for this event.', 'nexdirectory')
                        : sprintf(__('No followers yet for this %s.', 'nexdirectory'), strtolower((string) $singular_label));
                }
            }
        }

        return $this->render_frontend_template('whos-attending.php', [
            'ui_labels' => $ui_labels,
            'profile_key' => $profile_key,
            'page_title' => $page_title,
            'page_description' => $page_description,
            'events_url' => $events_url,
            'event_id' => $event_id,
            'event_title' => $event_title,
            'event_url' => $event_url,
            'rows' => $rows,
            'status_filter' => $status_filter,
            'confirmed_count' => $confirmed_count,
            'waitlist_count' => $waitlist_count,
            'state_message' => $state_message,
            'is_events_profile' => $is_events_profile,
        ]);
    }


    public function render_my_tickets($atts = []) {
        $atts = shortcode_atts([
            'limit' => 200,
        ], (array) $atts, 'eep_my_tickets');
        $ui_labels = $this->get_site_profile_ui_labels();
        $attending_title = sanitize_text_field((string) ($ui_labels['attending_title'] ?? __('Saved Listings', 'nexdirectory')));
        return $this->render_account_app_template([
            'initial_tab' => 'saved',
            'tickets_limit' => max(1, (int) $atts['limit']),
            'login_message' => sprintf(__('Please log in to access %s.', 'nexdirectory'), $attending_title),
        ]);
    }


    public function render_owner_analytics($atts = []) {
        $atts = shortcode_atts([
            'days' => 30,
        ], (array) $atts, 'eep_owner_analytics');
        $ui_labels = $this->get_site_profile_ui_labels();
        $analytics_title = sanitize_text_field((string) ($ui_labels['owner_analytics_title'] ?? __('Analytics', 'nexdirectory')));
        return $this->render_account_app_template([
            'initial_tab' => 'analytics',
            'analytics_days' => absint((int) $atts['days']),
            'login_message' => sprintf(__('Please log in to access %s.', 'nexdirectory'), $analytics_title),
        ]);
    }


    public function render_notifications_center($atts = []) {
        $atts = shortcode_atts([
            'limit' => 80,
        ], (array) $atts, 'eep_notifications');
        return $this->render_account_app_template([
            'initial_tab' => 'inbox',
            'notifications_limit' => max(1, min(300, (int) $atts['limit'])),
            'login_message' => __('Please log in to access your inbox.', 'nexdirectory'),
        ]);
    }


    public function handle_export_event_attendees() {
        if (!is_user_logged_in()) {
            wp_die(__('Login required.', 'nexdirectory'));
        }
        $post_id = isset($_POST['event_id']) ? absint((int) $_POST['event_id']) : 0;
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            wp_die(__('Invalid event.', 'nexdirectory'));
        }
        if (!isset($_POST['eep_export_attendees_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_export_attendees_nonce'])), 'eep_export_event_attendees_' . $post_id)) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }

        $owner_id = (int) get_post_field('post_author', $post_id);
        if ($owner_id !== get_current_user_id() && !$this->user_can_moderate_events()) {
            wp_die(__('You cannot export attendees for this event.', 'nexdirectory'));
        }

        $rows = $this->get_event_attendee_rows($post_id);
        nocache_headers();
        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename="' . sanitize_file_name('event-attendees-' . $post_id . '-' . gmdate('Ymd-His') . '.csv') . '"');

        $output = fopen('php://output', 'w');
        if ($output) {
            fputcsv($output, ['Name', 'Email', 'Status', 'Communication', 'Ticket ID', 'Ticket Status', 'Ticket Total', 'Payment Method', 'Gateway', 'Purchased', 'Ticket Verify URL', 'Ticket QR URL', 'Updated']);
            foreach ($rows as $row) {
                fputcsv($output, [
                    (string) ($row['name'] ?? ''),
                    (string) ($row['email'] ?? ''),
                    (string) ($row['status_label'] ?? ''),
                    (string) ($row['communication_label'] ?? ''),
                    (string) ($row['ticket_uid'] ?? ''),
                    (string) ($row['ticket_payment_status_label'] ?? ''),
                    (string) ($row['ticket_total_label'] ?? ''),
                    (string) ($row['ticket_payment_method'] ?? ''),
                    (string) ($row['ticket_gateway_label'] ?? ''),
                    (string) ($row['ticket_purchased_label'] ?? ''),
                    (string) ($row['ticket_verify_url'] ?? ''),
                    (string) ($row['ticket_qr_image_url'] ?? ''),
                    (string) ($row['updated_label'] ?? ''),
                ]);
            }
            fclose($output);
        }
        exit;
    }


    public function handle_email_event_attendees() {
        if (!is_user_logged_in()) {
            wp_die(__('Login required.', 'nexdirectory'));
        }
        if (!$this->user_can_access_platform(get_current_user_id())) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }

        $post_id = isset($_POST['event_id']) ? absint((int) $_POST['event_id']) : 0;
        $profile_key = $this->resolve_profile_key_for_post($post_id);
        if ($profile_key === '') {
            $profile_key = $this->get_site_profile_key();
        }
        $my_events_url = $this->get_platform_page_url(
            'eep_page_my_events_url',
            $this->get_profile_route_slugs('my_items', $profile_key),
            $this->get_profile_route_default_url('my_items', $profile_key)
        );
        $fallback_redirect = $post_id > 0 ? eep_add_query_arg('attendees_event', (string) $post_id, $my_events_url) : $my_events_url;
        $redirect = wp_get_referer() ?: $fallback_redirect;

        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            $this->redirect_with_message($redirect, __('Invalid event.', 'nexdirectory'), 'error');
        }
        if (!isset($_POST['eep_email_attendees_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_email_attendees_nonce'])), 'eep_email_event_attendees_' . $post_id)) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }

        $owner_id = (int) get_post_field('post_author', $post_id);
        if ($owner_id !== get_current_user_id() && !$this->user_can_moderate_events()) {
            wp_die(__('You cannot message attendees for this event.', 'nexdirectory'));
        }

        $audience = sanitize_key((string) wp_unslash($_POST['audience'] ?? 'confirmed'));
        if (!in_array($audience, ['confirmed', 'waitlist', 'all'], true)) {
            $audience = 'confirmed';
        }
        $subject_template = sanitize_text_field((string) wp_unslash($_POST['subject'] ?? ''));
        $message_template = trim((string) wp_unslash($_POST['message'] ?? ''));
        $send_copy = (string) wp_unslash($_POST['send_copy'] ?? '0') === '1';

        if ($subject_template === '' || $message_template === '') {
            $this->redirect_with_message($redirect, __('Subject and message are required.', 'nexdirectory'), 'error');
        }
        if (function_exists('mb_strlen') ? mb_strlen($subject_template) > 160 : strlen($subject_template) > 160) {
            $this->redirect_with_message($redirect, __('Subject must be 160 characters or fewer.', 'nexdirectory'), 'error');
        }
        if (function_exists('mb_strlen') ? mb_strlen($message_template) > 6000 : strlen($message_template) > 6000) {
            $this->redirect_with_message($redirect, __('Message must be 6000 characters or fewer.', 'nexdirectory'), 'error');
        }

        $rows = $this->get_event_attendee_rows($post_id);
        if (empty($rows)) {
            $this->redirect_with_message($redirect, __('No attendees are available for messaging yet.', 'nexdirectory'), 'error');
        }

        $status_filter = '';
        if ($audience === 'confirmed') {
            $status_filter = 'yes';
        } elseif ($audience === 'waitlist') {
            $status_filter = 'waitlist';
        }

        $sender = wp_get_current_user();
        $sender_name = sanitize_text_field((string) ($sender->display_name ?: $sender->user_login));
        $sender_email = sanitize_email((string) ($sender->user_email ?? ''));
        $event_title = (string) get_the_title($post_id);
        $event_url = (string) get_permalink($post_id);
        $site_name = sanitize_text_field((string) $this->get_white_label_brand_name());

        $sent_count = 0;
        $failed_count = 0;
        $skipped_no_consent = 0;
        $skipped_invalid = 0;
        $notification_failed_count = 0;
        $failed_reasons = [];
        $processed = [];

        foreach ($rows as $row) {
            $row_status = sanitize_key((string) ($row['status'] ?? ''));
            if ($status_filter !== '' && $row_status !== $status_filter) {
                continue;
            }
            $recipient_id = absint((int) ($row['user_id'] ?? 0));
            if ($recipient_id <= 0 || isset($processed[$recipient_id])) {
                continue;
            }
            $processed[$recipient_id] = true;

            if (!$this->user_allows_organizer_communications($recipient_id)) {
                $skipped_no_consent++;
                continue;
            }

            $recipient = get_user_by('id', $recipient_id);
            if (!$recipient instanceof WP_User) {
                $skipped_invalid++;
                continue;
            }

            $tokens = [
                '{event_title}' => $event_title,
                '{event_url}' => $event_url,
                '{recipient_name}' => (string) ($recipient->display_name ?: $recipient->user_login),
                '{attendee_name}' => (string) ($recipient->display_name ?: $recipient->user_login),
                '{organizer_name}' => $sender_name,
                '{site_name}' => $site_name,
            ];
            $subject = eep_sanitize_text(strtr($subject_template, $tokens));
            if ($subject === '') {
                $subject = sprintf(__('Update about %s', 'nexdirectory'), $event_title);
            }

            $rendered_message = eep_sanitize_textarea((string) strtr($message_template, $tokens));
            if ($rendered_message === '') {
                $rendered_message = sprintf(
                    __('Update about %1$s: %2$s', 'nexdirectory'),
                    $event_title,
                    $event_url
                );
            }

            $message_result = $this->inbox_send_message($post_id, get_current_user_id(), $recipient_id, $subject, $rendered_message);
            if (is_wp_error($message_result)) {
                $failed_count++;
                $error_code = sanitize_key((string) $message_result->get_error_code());
                if ($error_code === '') {
                    $error_code = 'unknown_error';
                }
                $failed_reasons[$error_code] = (int) ($failed_reasons[$error_code] ?? 0) + 1;
                $this->append_runtime_log('warning', 'Attendee inbox delivery failed', [
                    'post_id' => (string) $post_id,
                    'recipient_id' => (string) $recipient_id,
                    'error_code' => (string) $error_code,
                    'error_message' => (string) $message_result->get_error_message(),
                ]);
                continue;
            }
            $sent_count++;
            $thread_id = absint((int) ($message_result['thread_id'] ?? 0));
            $message_id = absint((int) ($message_result['message_id'] ?? 0));
            $notification_sent = $this->send_inbox_message_notification_email(
                $recipient_id,
                get_current_user_id(),
                $post_id,
                $thread_id,
                $message_id,
                $subject
            );
            if (!$notification_sent) {
                $notification_failed_count++;
            }
        }

        $copy_inbox_sent = false;
        $copy_email_sent = false;
        if ($send_copy) {
            $copy_subject = sprintf(__('Copy: attendee message for %s', 'nexdirectory'), $event_title);
            $copy_body = sprintf(
                "%s\n\n%s\n\n%s",
                __('This is a copy of the message sent to consented attendees.', 'nexdirectory'),
                $subject_template,
                $message_template
            );

            $copy_inbox_result = $this->inbox_send_message(
                $post_id,
                get_current_user_id(),
                get_current_user_id(),
                $copy_subject,
                eep_sanitize_textarea($copy_body)
            );
            $copy_inbox_sent = !is_wp_error($copy_inbox_result);
            if (!$copy_inbox_sent) {
                $this->append_runtime_log('warning', 'Attendee copy inbox delivery failed', [
                    'post_id' => (string) $post_id,
                    'sender_id' => (string) get_current_user_id(),
                    'error_code' => is_wp_error($copy_inbox_result) ? (string) $copy_inbox_result->get_error_code() : 'unknown_error',
                    'error_message' => is_wp_error($copy_inbox_result) ? (string) $copy_inbox_result->get_error_message() : 'unknown',
                ]);
            }

            if (is_email($sender_email)) {
                $copy_email_sent = (bool) $this->send_platform_email($sender_email, $copy_subject, '', [
                    'post_id' => $post_id,
                    'event_title' => $event_title,
                    'event_url' => $event_url,
                    'custom_body_html' => wpautop(wp_kses_post($copy_body)),
                    'fallback_text' => eep_sanitize_textarea($copy_body),
                ]);
            }
        }

        $this->append_runtime_log('info', 'Attendee communication campaign processed', [
            'post_id' => (string) $post_id,
            'audience' => $audience,
            'sent' => (string) $sent_count,
            'failed' => (string) $failed_count,
            'skipped_no_consent' => (string) $skipped_no_consent,
            'skipped_invalid' => (string) $skipped_invalid,
            'notification_failed' => (string) $notification_failed_count,
            'failed_reasons' => wp_json_encode($failed_reasons),
        ]);

        $message = sprintf(
            __('Campaign processed. Inbox messages sent: %1$d. Failed: %2$d. Skipped (GDPR opt-out): %3$d. Skipped (invalid): %4$d.', 'nexdirectory'),
            $sent_count,
            $failed_count,
            $skipped_no_consent,
            $skipped_invalid
        );
        if ($notification_failed_count > 0) {
            $message .= ' ' . sprintf(
                __('Notification emails not delivered: %d.', 'nexdirectory'),
                $notification_failed_count
            );
        }
        if ($failed_count > 0 && !empty($failed_reasons)) {
            arsort($failed_reasons);
            $summary_bits = [];
            foreach ($failed_reasons as $reason => $reason_count) {
                $summary_bits[] = $reason . ': ' . (int) $reason_count;
                if (count($summary_bits) >= 3) {
                    break;
                }
            }
            if (!empty($summary_bits)) {
                $message .= ' ' . sprintf(
                    __('Failure reasons: %s.', 'nexdirectory'),
                    implode(', ', $summary_bits)
                );
            }
        }
        if ($send_copy && $copy_inbox_sent && $copy_email_sent) {
            $message .= ' ' . __('A copy was added to your inbox and sent to your account email.', 'nexdirectory');
        } elseif ($send_copy && $copy_inbox_sent && !$copy_email_sent) {
            $message .= ' ' . __('A copy was added to your inbox. Email copy could not be delivered.', 'nexdirectory');
        } elseif ($send_copy && !$copy_inbox_sent && $copy_email_sent) {
            $message .= ' ' . __('A copy was sent to your account email.', 'nexdirectory');
        } elseif ($send_copy) {
            $message .= ' ' . __('Could not send a copy to your inbox or account email.', 'nexdirectory');
        }

        $notice_type = $sent_count > 0 ? 'success' : 'error';
        $this->redirect_with_message($redirect, $message, $notice_type);
    }


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


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


    private function normalize_inbox_participants($user_a, $user_b) {
        $user_a = absint((int) $user_a);
        $user_b = absint((int) $user_b);
        if ($user_a <= 0 || $user_b <= 0) {
            return [0, 0];
        }
        if ($user_a <= $user_b) {
            return [$user_a, $user_b];
        }
        return [$user_b, $user_a];
    }


    private function get_inbox_thread_id_for_participants($post_id, $participant_one, $participant_two) {
        $post_id = absint((int) $post_id);
        [$participant_a, $participant_b] = $this->normalize_inbox_participants($participant_one, $participant_two);
        if ($post_id <= 0 || $participant_a <= 0 || $participant_b <= 0) {
            return 0;
        }

        global $wpdb;
        $threads_table = $this->get_inbox_threads_table_name();
        return absint((int) $wpdb->get_var($wpdb->prepare(
            "SELECT id FROM {$threads_table} WHERE post_id = %d AND participant_a = %d AND participant_b = %d LIMIT 1",
            $post_id,
            $participant_a,
            $participant_b
        )));
    }


    private function get_or_create_inbox_thread($post_id, $participant_one, $participant_two, $subject = '') {
        $post_id = absint((int) $post_id);
        [$participant_a, $participant_b] = $this->normalize_inbox_participants($participant_one, $participant_two);
        if ($post_id <= 0 || $participant_a <= 0 || $participant_b <= 0) {
            return new WP_Error('eep_inbox_invalid_thread', __('Invalid inbox thread request.', 'nexdirectory'));
        }

        global $wpdb;
        $threads_table = $this->get_inbox_threads_table_name();
        $existing_thread_id = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT id FROM {$threads_table} WHERE post_id = %d AND participant_a = %d AND participant_b = %d LIMIT 1",
            $post_id,
            $participant_a,
            $participant_b
        ));
        if ($existing_thread_id > 0) {
            if ($subject !== '') {
                $wpdb->update(
                    $threads_table,
                    ['subject' => sanitize_text_field((string) $subject)],
                    ['id' => $existing_thread_id],
                    ['%s'],
                    ['%d']
                );
            }
            return $existing_thread_id;
        }

        $now = current_time('mysql', 1);
        $inserted = $wpdb->insert($threads_table, [
            'post_id' => $post_id,
            'participant_a' => $participant_a,
            'participant_b' => $participant_b,
            'subject' => sanitize_text_field((string) $subject),
            'created_at' => $now,
            'updated_at' => $now,
            'last_message_id' => 0,
            'last_sender_id' => 0,
        ], ['%d', '%d', '%d', '%s', '%s', '%s', '%d', '%d']);
        if ($inserted === false) {
            $existing_thread_id = (int) $wpdb->get_var($wpdb->prepare(
                "SELECT id FROM {$threads_table} WHERE post_id = %d AND participant_a = %d AND participant_b = %d LIMIT 1",
                $post_id,
                $participant_a,
                $participant_b
            ));
            if ($existing_thread_id > 0) {
                return $existing_thread_id;
            }
            return new WP_Error('eep_inbox_thread_create_failed', __('Could not start inbox thread.', 'nexdirectory'));
        }

        return (int) $wpdb->insert_id;
    }


    private function inbox_send_message($post_id, $sender_id, $recipient_id, $subject, $body, $attachment_url = '') {
        $post_id = absint((int) $post_id);
        $sender_id = absint((int) $sender_id);
        $recipient_id = absint((int) $recipient_id);
        $subject = sanitize_text_field((string) $subject);
        $body = trim((string) eep_sanitize_textarea((string) $body));
        $attachment_url = esc_url_raw((string) $attachment_url);
        if ($post_id <= 0 || $sender_id <= 0 || $recipient_id <= 0 || $body === '') {
            return new WP_Error('eep_inbox_invalid_message', __('Invalid inbox message request.', 'nexdirectory'));
        }

        $thread_id = $this->get_or_create_inbox_thread($post_id, $sender_id, $recipient_id, $subject);
        if (is_wp_error($thread_id)) {
            return $thread_id;
        }

        global $wpdb;
        $messages_table = $this->get_inbox_messages_table_name();
        $threads_table = $this->get_inbox_threads_table_name();
        $now = current_time('mysql', 1);
        $read_at = $sender_id === $recipient_id ? $now : null;
        $inserted = $wpdb->insert($messages_table, [
            'thread_id' => (int) $thread_id,
            'post_id' => $post_id,
            'sender_id' => $sender_id,
            'recipient_id' => $recipient_id,
            'subject' => $subject,
            'body' => $body,
            'attachment_url' => $attachment_url,
            'created_at' => $now,
            'read_at' => $read_at,
        ], ['%d', '%d', '%d', '%d', '%s', '%s', '%s', '%s', '%s']);
        if ($inserted === false) {
            return new WP_Error('eep_inbox_message_create_failed', __('Could not send inbox message.', 'nexdirectory'));
        }
        $message_id = (int) $wpdb->insert_id;

        $wpdb->update($threads_table, [
            'updated_at' => $now,
            'last_message_id' => $message_id,
            'last_sender_id' => $sender_id,
            'subject' => $subject,
        ], ['id' => (int) $thread_id], ['%s', '%d', '%d', '%s'], ['%d']);

        return [
            'thread_id' => (int) $thread_id,
            'message_id' => $message_id,
        ];
    }


    private function get_notifications_page_url() {
        $base_url = (string) $this->get_platform_page_url(
            'eep_page_account_url',
            $this->get_profile_route_slugs('account'),
            $this->get_profile_route_default_url('account')
        );
        if ($base_url === '') {
            $base_url = (string) $this->get_platform_page_url(
                'eep_page_submit_event_url',
                $this->get_profile_route_slugs('submit'),
                $this->get_profile_route_default_url('submit')
            );
        }
        if ($base_url === '') {
            $base_url = (string) home_url('/submit/');
        }
        return esc_url_raw($base_url);
    }


    private function build_inbox_thread_url($thread_id = 0, $message_id = 0) {
        $thread_id = absint((int) $thread_id);
        $message_id = absint((int) $message_id);
        $base_url = $this->get_notifications_page_url();
        $query_args = [
            'eep_tab' => 'inbox',
        ];
        if ($thread_id > 0) {
            $query_args['thread'] = $thread_id;
        }
        if ($message_id > 0) {
            $query_args['message'] = $message_id;
        }
        $url = !empty($query_args) ? eep_add_query_arg($query_args, $base_url) : $base_url;
        if (strpos($url, '#') === false) {
            $url .= '#inbox';
        }
        return $url;
    }


    private function send_inbox_message_notification_email($recipient_id, $sender_id, $post_id, $thread_id, $message_id, $subject_line = '') {
        $recipient_id = absint((int) $recipient_id);
        $sender_id = absint((int) $sender_id);
        $post_id = absint((int) $post_id);
        $thread_id = absint((int) $thread_id);
        $message_id = absint((int) $message_id);
        if ($recipient_id <= 0 || $sender_id <= 0 || $post_id <= 0 || $thread_id <= 0 || $message_id <= 0) {
            return false;
        }

        $recipient = get_user_by('id', $recipient_id);
        if (!$recipient instanceof WP_User || !is_email((string) $recipient->user_email)) {
            return false;
        }
        $sender = get_user_by('id', $sender_id);
        $sender_name = $sender instanceof WP_User ? sanitize_text_field((string) ($sender->display_name ?: $sender->user_login)) : __('A member', 'nexdirectory');
        $event_title = sanitize_text_field((string) get_the_title($post_id));
        $thread_url = $this->build_inbox_thread_url($thread_id, $message_id);
        $subject = $subject_line !== ''
            ? sanitize_text_field((string) $subject_line)
            : sprintf(__('New message from %1$s about %2$s', 'nexdirectory'), $sender_name, $event_title);
        $body = sprintf(
            __("You received a new inbox message from %1\$s about \"%2\$s\".\n\nOpen your inbox:\n%3\$s", 'nexdirectory'),
            $sender_name,
            $event_title,
            $thread_url
        );

        return (bool) $this->send_platform_email((string) $recipient->user_email, $subject, '', [
            'post_id' => $post_id,
            'event_title' => $event_title,
            'event_url' => $thread_url,
            'custom_body_html' => wpautop(wp_kses_post($body)),
            'fallback_text' => eep_sanitize_textarea((string) $body),
        ]);
    }


    private function get_inbox_unread_count_for_user($user_id = 0) {
        $user_id = absint((int) $user_id);
        if ($user_id <= 0) {
            return 0;
        }
        global $wpdb;
        $messages_table = $this->get_inbox_messages_table_name();
        return max(0, (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$messages_table} WHERE recipient_id = %d AND read_at IS NULL",
            $user_id
        )));
    }


    private function get_inbox_threads_for_user($user_id, $limit = 60) {
        $user_id = absint((int) $user_id);
        $limit = max(1, min(300, (int) $limit));
        if ($user_id <= 0) {
            return [];
        }

        global $wpdb;
        $threads_table = $this->get_inbox_threads_table_name();
        $messages_table = $this->get_inbox_messages_table_name();
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT t.id, t.post_id, t.participant_a, t.participant_b, t.subject, t.updated_at, t.last_message_id, t.last_sender_id, m.body AS last_body, m.created_at AS last_created_at, m.subject AS last_subject
             FROM {$threads_table} t
             LEFT JOIN {$messages_table} m ON m.id = t.last_message_id
             WHERE t.participant_a = %d OR t.participant_b = %d
             ORDER BY t.updated_at DESC
             LIMIT %d",
            $user_id,
            $user_id,
            $limit
        ), ARRAY_A);
        if (!is_array($rows) || empty($rows)) {
            return [];
        }

        $thread_ids = [];
        $other_user_ids = [];
        foreach ($rows as $row) {
            $thread_id = absint((int) ($row['id'] ?? 0));
            if ($thread_id > 0) {
                $thread_ids[] = $thread_id;
            }
            $participant_a = absint((int) ($row['participant_a'] ?? 0));
            $participant_b = absint((int) ($row['participant_b'] ?? 0));
            $other_id = $participant_a === $user_id ? $participant_b : $participant_a;
            if ($other_id > 0) {
                $other_user_ids[] = $other_id;
            }
        }
        $thread_ids = array_values(array_unique($thread_ids));
        $other_user_ids = array_values(array_unique($other_user_ids));

        $unread_map = [];
        if (!empty($thread_ids)) {
            $id_list_sql = implode(',', array_map('absint', $thread_ids));
            $unread_rows = $wpdb->get_results($wpdb->prepare(
                "SELECT thread_id, COUNT(*) AS unread_count FROM {$messages_table} WHERE recipient_id = %d AND read_at IS NULL AND thread_id IN ({$id_list_sql}) GROUP BY thread_id",
                $user_id
            ), ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            if (is_array($unread_rows)) {
                foreach ($unread_rows as $unread_row) {
                    $unread_map[absint((int) ($unread_row['thread_id'] ?? 0))] = max(0, (int) ($unread_row['unread_count'] ?? 0));
                }
            }
        }

        $users_map = [];
        if (!empty($other_user_ids)) {
            $users = get_users([
                'include' => $other_user_ids,
                'fields' => ['ID', 'display_name', 'user_login'],
            ]);
            foreach ((array) $users as $user) {
                if (!$user instanceof WP_User) {
                    continue;
                }
                $users_map[(int) $user->ID] = sanitize_text_field((string) ($user->display_name ?: $user->user_login));
            }
        }

        $out = [];
        foreach ($rows as $row) {
            $thread_id = absint((int) ($row['id'] ?? 0));
            if ($thread_id <= 0) {
                continue;
            }
            $participant_a = absint((int) ($row['participant_a'] ?? 0));
            $participant_b = absint((int) ($row['participant_b'] ?? 0));
            $other_id = $participant_a === $user_id ? $participant_b : $participant_a;
            $last_created_ts = strtotime((string) ($row['last_created_at'] ?? ''));
            $post_id = absint((int) ($row['post_id'] ?? 0));
            $event_title = $post_id > 0 ? (string) get_the_title($post_id) : '';
            $out[] = [
                'thread_id' => $thread_id,
                'post_id' => $post_id,
                'event_title' => $event_title,
                'subject' => sanitize_text_field((string) (($row['last_subject'] ?? '') !== '' ? $row['last_subject'] : ($row['subject'] ?? ''))),
                'last_body' => sanitize_text_field((string) wp_trim_words((string) ($row['last_body'] ?? ''), 18)),
                'last_time' => $last_created_ts ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), $last_created_ts) : '',
                'last_sender_id' => absint((int) ($row['last_sender_id'] ?? 0)),
                'other_user_id' => $other_id,
                'other_name' => (string) ($users_map[$other_id] ?? __('Member', 'nexdirectory')),
                'unread_count' => (int) ($unread_map[$thread_id] ?? 0),
                'thread_url' => $this->build_inbox_thread_url($thread_id, 0),
            ];
        }

        return $out;
    }


    private function get_inbox_thread_for_user($thread_id, $user_id) {
        $thread_id = absint((int) $thread_id);
        $user_id = absint((int) $user_id);
        if ($thread_id <= 0 || $user_id <= 0) {
            return null;
        }

        global $wpdb;
        $threads_table = $this->get_inbox_threads_table_name();
        $row = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$threads_table} WHERE id = %d LIMIT 1",
            $thread_id
        ), ARRAY_A);
        if (!is_array($row)) {
            return null;
        }
        $participant_a = absint((int) ($row['participant_a'] ?? 0));
        $participant_b = absint((int) ($row['participant_b'] ?? 0));
        if ($participant_a !== $user_id && $participant_b !== $user_id) {
            return null;
        }
        return $row;
    }


    private function mark_inbox_thread_read_for_user($thread_id, $user_id) {
        $thread_id = absint((int) $thread_id);
        $user_id = absint((int) $user_id);
        if ($thread_id <= 0 || $user_id <= 0) {
            return;
        }
        global $wpdb;
        $messages_table = $this->get_inbox_messages_table_name();
        $now = current_time('mysql', 1);
        $wpdb->query($wpdb->prepare(
            "UPDATE {$messages_table} SET read_at = %s WHERE thread_id = %d AND recipient_id = %d AND read_at IS NULL",
            $now,
            $thread_id,
            $user_id
        )); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
    }


    private function get_inbox_messages_for_thread($thread_id, $limit = 400) {
        $thread_id = absint((int) $thread_id);
        $limit = max(1, min(2000, (int) $limit));
        if ($thread_id <= 0) {
            return [];
        }
        global $wpdb;
        $messages_table = $this->get_inbox_messages_table_name();
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT id, thread_id, post_id, sender_id, recipient_id, subject, body, attachment_url, created_at, read_at
             FROM {$messages_table}
             WHERE thread_id = %d
             ORDER BY created_at ASC, id ASC
             LIMIT %d",
            $thread_id,
            $limit
        ), ARRAY_A);
        return is_array($rows) ? $rows : [];
    }


    private function get_inbox_latest_unread_for_user($user_id) {
        $user_id = absint((int) $user_id);
        if ($user_id <= 0) {
            return [];
        }
        global $wpdb;
        $messages_table = $this->get_inbox_messages_table_name();
        $row = $wpdb->get_row($wpdb->prepare(
            "SELECT id, thread_id, post_id, sender_id, subject, created_at
             FROM {$messages_table}
             WHERE recipient_id = %d AND read_at IS NULL
             ORDER BY id DESC
             LIMIT 1",
            $user_id
        ), ARRAY_A);
        return is_array($row) ? $row : [];
    }


    public function handle_inbox_reply() {
        $redirect = isset($_POST['redirect_to']) ? esc_url_raw((string) wp_unslash($_POST['redirect_to'])) : '';
        if ($redirect === '') {
            $redirect = wp_get_referer() ?: home_url('/');
        }

        if (!is_user_logged_in()) {
            wp_safe_redirect(wp_login_url($redirect));
            exit;
        }
        if (!$this->user_can_access_platform(get_current_user_id())) {
            $this->redirect_with_message($redirect, __('Not allowed.', 'nexdirectory'), 'error');
        }

        $thread_id = isset($_POST['thread_id']) ? absint((int) $_POST['thread_id']) : 0;
        if ($thread_id <= 0) {
            $this->redirect_with_message($redirect, __('Invalid inbox thread.', 'nexdirectory'), 'error');
        }
        if (!isset($_POST['eep_inbox_reply_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_inbox_reply_nonce'])), 'eep_inbox_reply_' . $thread_id)) {
            $this->redirect_with_message($redirect, __('Invalid request.', 'nexdirectory'), 'error');
        }

        $current_user_id = (int) get_current_user_id();
        $thread = $this->get_inbox_thread_for_user($thread_id, $current_user_id);
        if (!is_array($thread) || empty($thread)) {
            $this->redirect_with_message($redirect, __('You cannot reply to this inbox thread.', 'nexdirectory'), 'error');
        }
        $message = eep_sanitize_textarea((string) wp_unslash($_POST['message'] ?? ''));
        if (trim($message) === '') {
            $this->redirect_with_message($redirect, __('Message content is required.', 'nexdirectory'), 'error');
        }

        $participant_a = absint((int) ($thread['participant_a'] ?? 0));
        $participant_b = absint((int) ($thread['participant_b'] ?? 0));
        $recipient_id = $participant_a === $current_user_id ? $participant_b : $participant_a;
        if ($recipient_id <= 0 || $recipient_id === $current_user_id) {
            $this->redirect_with_message($redirect, __('Invalid inbox recipient.', 'nexdirectory'), 'error');
        }

        $post_id = absint((int) ($thread['post_id'] ?? 0));
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            $this->redirect_with_message($redirect, __('Inbox thread is no longer available.', 'nexdirectory'), 'error');
        }

        $subject = sanitize_text_field((string) wp_unslash($_POST['subject'] ?? ''));
        if ($subject === '') {
            $subject = sanitize_text_field((string) ($thread['subject'] ?? ''));
        }
        if ($subject === '') {
            $subject = sprintf(__('Message about %s', 'nexdirectory'), get_the_title($post_id));
        }

        $send_result = $this->inbox_send_message($post_id, $current_user_id, $recipient_id, $subject, $message);
        if (is_wp_error($send_result)) {
            $this->redirect_with_message($redirect, $send_result->get_error_message(), 'error');
        }

        $message_id = absint((int) ($send_result['message_id'] ?? 0));
        $this->send_inbox_message_notification_email(
            $recipient_id,
            $current_user_id,
            $post_id,
            $thread_id,
            $message_id,
            $subject
        );

        $this->redirect_with_message(
            $this->build_inbox_thread_url($thread_id, $message_id),
            __('Reply sent.', 'nexdirectory')
        );
    }


    public function render_events_calendar($atts = []) {
        $atts = shortcode_atts([
            'max_events' => 300,
        ], (array) $atts, 'eep_events_calendar');
        $max_events = max(10, min(1000, (int) $atts['max_events']));
        $ui_labels = $this->get_site_profile_ui_labels();
        $profile_key = sanitize_key((string) ($ui_labels['profile_key'] ?? ''));

        $requested_month = isset($_GET['eep_month']) ? sanitize_text_field((string) wp_unslash($_GET['eep_month'])) : '';
        if (!preg_match('/^\d{4}\-\d{2}$/', $requested_month)) {
            $requested_month = wp_date('Y-m');
        }

        $tz = wp_timezone();
        try {
            $month_cursor = new DateTimeImmutable($requested_month . '-01 00:00:00', $tz);
        } catch (Exception $e) {
            $month_cursor = new DateTimeImmutable('first day of this month 00:00:00', $tz);
        }
        $month_start = $month_cursor->setTime(0, 0, 0);
        $next_month = $month_start->modify('first day of next month');
        $prev_month = $month_start->modify('first day of previous month');
        $month_end_day = $next_month->modify('-1 day')->setTime(0, 0, 0);
        $month_start_sql = $month_start->format('Y-m-d H:i:s');
        $next_month_sql = $next_month->format('Y-m-d H:i:s');

        $query = new WP_Query([
            'post_type' => self::POST_TYPE,
            'post_status' => 'publish',
            'posts_per_page' => $max_events,
            'meta_key' => 'eep_start',
            'orderby' => 'meta_value',
            'order' => 'ASC',
            'meta_query' => [
                'relation' => 'AND',
                [
                    'key' => 'eep_start',
                    'value' => $next_month_sql,
                    'compare' => '<',
                    'type' => 'DATETIME',
                ],
                [
                    'relation' => 'OR',
                    [
                        'key' => 'eep_end',
                        'value' => $month_start_sql,
                        'compare' => '>=',
                        'type' => 'DATETIME',
                    ],
                    [
                        'key' => 'eep_start',
                        'value' => $month_start_sql,
                        'compare' => '>=',
                        'type' => 'DATETIME',
                    ],
                ],
            ],
        ]);

        $events_by_day = [];
        $decode_calendar_text = static function ($raw) {
            $value = eep_strip_all_tags((string) $raw, true);
            // Decode twice so strings like "&amp;#8211;" resolve to the final glyph.
            $value = html_entity_decode($value, ENT_QUOTES, 'UTF-8');
            $value = html_entity_decode($value, ENT_QUOTES, 'UTF-8');
            return eep_sanitize_text($value);
        };
        if ($query->have_posts()) {
            while ($query->have_posts()) {
                $query->the_post();
                $event_id = get_the_ID();
                $start_raw = (string) get_post_meta($event_id, 'eep_start', true);
                $end_raw = (string) get_post_meta($event_id, 'eep_end', true);
                $start_ts = strtotime($start_raw);
                if (!$start_ts) {
                    continue;
                }
                $end_ts = $end_raw !== '' ? strtotime($end_raw) : false;
                if (!$end_ts || $end_ts < $start_ts) {
                    $end_ts = $start_ts;
                }

                $event_start = (new DateTimeImmutable('@' . $start_ts))->setTimezone($tz);
                $event_end = (new DateTimeImmutable('@' . $end_ts))->setTimezone($tz);
                $event_start_day = $event_start->setTime(0, 0, 0);
                $event_end_day = $event_end->setTime(0, 0, 0);
                $is_multi_day = $event_end_day > $event_start_day;

                $range_start = $event_start_day < $month_start ? $month_start : $event_start_day;
                $range_end = $event_end_day > $month_end_day ? $month_end_day : $event_end_day;
                if ($range_end < $range_start) {
                    continue;
                }

                $city = $decode_calendar_text((string) get_post_meta($event_id, 'eep_city', true));
                $title = $decode_calendar_text((string) get_the_title());
                $event_title = $title !== '' ? $title : sanitize_text_field((string) __('Untitled Event', 'nexdirectory'));
                $event_url = get_permalink();
                $start_day_key = $event_start_day->format('Y-m-d');
                $end_day_key = $event_end_day->format('Y-m-d');

                for ($cursor = $range_start; $cursor <= $range_end; $cursor = $cursor->modify('+1 day')) {
                    $day_key = $cursor->format('Y-m-d');
                    if (!isset($events_by_day[$day_key])) {
                        $events_by_day[$day_key] = [];
                    }

                    $is_start_day = $day_key === $start_day_key;
                    $is_end_day = $day_key === $end_day_key;
                    $time_label = '';
                    if ($is_start_day && $this->event_datetime_has_time($start_raw)) {
                        $time_label = wp_date(get_option('time_format'), $start_ts);
                    } elseif ($is_end_day && $is_multi_day && $this->event_datetime_has_time($end_raw)) {
                        $time_label = sprintf(
                            /* translators: %s: time */
                            __('Ends %s', 'nexdirectory'),
                            wp_date(get_option('time_format'), $end_ts)
                        );
                    } elseif ($is_multi_day) {
                        $time_label = __('Multi-day', 'nexdirectory');
                    }

                    $events_by_day[$day_key][] = [
                        'id' => $event_id,
                        'title' => $event_title,
                        'url' => $event_url,
                        'time' => $time_label,
                        'city' => $city,
                        'type' => 'event',
                        'is_multi_day' => $is_multi_day,
                    ];
                }
            }
        }
        wp_reset_postdata();

        $start_of_week = max(0, min(6, (int) get_option('start_of_week', 1)));
        $first_weekday = (int) $month_start->format('w');
        $leading_empty = ($first_weekday - $start_of_week + 7) % 7;
        $days_in_month = (int) $month_start->format('t');
        $today_key = wp_date('Y-m-d');

        $cells = [];
        for ($i = 0; $i < $leading_empty; $i++) {
            $cells[] = null;
        }
        for ($day = 1; $day <= $days_in_month; $day++) {
            $day_key = $month_start->setDate(
                (int) $month_start->format('Y'),
                (int) $month_start->format('m'),
                $day
            )->format('Y-m-d');
            $cells[] = [
                'day' => $day,
                'day_key' => $day_key,
                'is_today' => $day_key === $today_key,
                'events' => isset($events_by_day[$day_key]) ? $events_by_day[$day_key] : [],
            ];
        }
        while (count($cells) % 7 !== 0) {
            $cells[] = null;
        }

        $weekday_labels = [];
        for ($i = 0; $i < 7; $i++) {
            $weekday = ($start_of_week + $i) % 7;
            $weekday_labels[] = wp_date('D', strtotime('Sunday +' . $weekday . ' days'));
        }

        $base_url = eep_remove_query_arg('eep_month', (string) $this->current_url());
        $prev_url = eep_add_query_arg('eep_month', $prev_month->format('Y-m'), $base_url);
        $next_url = eep_add_query_arg('eep_month', $next_month->format('Y-m'), $base_url);

        return $this->render_frontend_template('events-calendar.php', [
            'month_label' => wp_date('F Y', $month_start->getTimestamp()),
            'month_key' => $month_start->format('Y-m'),
            'weekday_labels' => $weekday_labels,
            'cells' => $cells,
            'prev_url' => $prev_url,
            'next_url' => $next_url,
            'events_url' => $this->get_platform_page_url(
                'eep_page_events_url',
                $this->get_profile_route_slugs('events', $profile_key),
                $this->get_profile_route_default_url('events', $profile_key)
            ),
            'max_inline_per_day' => 3,
            'ui_labels' => $ui_labels,
        ]);
    }


    public function handle_upload_image() {
        if (!is_user_logged_in()) {
            wp_send_json_error(['message' => __('You are not allowed to upload files.', 'nexdirectory')], 403);
        }
        if (!current_user_can('upload_files') || !$this->user_can_submit_events(get_current_user_id())) {
            wp_send_json_error(['message' => __('You do not have permission to upload files.', 'nexdirectory')], 403);
        }
        if (!isset($_POST['eep_upload_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_upload_nonce'])), 'eep_upload_image_nonce')) {
            wp_send_json_error(['message' => __('Invalid upload request.', 'nexdirectory')], 400);
        }
        if (empty($_FILES['file']) || !is_array($_FILES['file'])) {
            wp_send_json_error(['message' => __('No image file received.', 'nexdirectory')], 400);
        }

        $file = $_FILES['file'];
        $size = isset($file['size']) ? absint((int) $file['size']) : 0;
        if ($size <= 0) {
            wp_send_json_error(['message' => __('Uploaded file is empty.', 'nexdirectory')], 400);
        }
        $upload_kind = sanitize_key((string) wp_unslash($_POST['eep_upload_kind'] ?? 'image'));
        if (!in_array($upload_kind, ['image', 'file'], true)) {
            $upload_kind = 'image';
        }
        $upload_target = sanitize_key((string) wp_unslash($_POST['eep_upload_target'] ?? 'gallery'));
        $limits = $this->get_upload_limits();
        $max_bytes = (int) ($limits['gallery_max_bytes'] ?? 2048000);
        if (in_array($upload_target, ['cover', 'custom_image'], true)) {
            $max_bytes = (int) ($limits['cover_max_bytes'] ?? 512000);
        } elseif (in_array($upload_target, ['custom_file', 'file'], true) || $upload_kind === 'file') {
            $max_bytes = (int) ($limits['file_max_bytes'] ?? 2097152);
        }
        if ($max_bytes > 0 && $size > $max_bytes) {
            $max_kb = max(1, (int) floor($max_bytes / 1024));
            wp_send_json_error([
                'message' => sprintf(__('File is too large. Max allowed size is %d KB.', 'nexdirectory'), $max_kb),
            ], 400);
        }

        $tmp_name = isset($file['tmp_name']) ? (string) $file['tmp_name'] : '';
        $name = isset($file['name']) ? (string) $file['name'] : '';
        $allowed_mimes = get_allowed_mime_types();
        $check = wp_check_filetype_and_ext($tmp_name, $name, $allowed_mimes);
        $mime = (string) ($check['type'] ?? '');
        if ($mime === '') {
            $basic = wp_check_filetype($name, $allowed_mimes);
            $mime = (string) ($basic['type'] ?? '');
        }
        if ($mime === '' && $upload_kind === 'image' && function_exists('getimagesize')) {
            $image_info = @getimagesize($tmp_name); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
            if (is_array($image_info) && !empty($image_info['mime']) && strpos((string) $image_info['mime'], 'image/') === 0) {
                $mime = (string) $image_info['mime'];
            }
        }
        if ($upload_kind === 'image') {
            if ($mime === '' || strpos($mime, 'image/') !== 0) {
                wp_send_json_error(['message' => __('Only image uploads are allowed for this field.', 'nexdirectory')], 400);
            }
        } elseif ($mime === '') {
            wp_send_json_error(['message' => __('This file type is not allowed.', 'nexdirectory')], 400);
        }

        require_once ABSPATH . 'wp-admin/includes/file.php';
        require_once ABSPATH . 'wp-admin/includes/image.php';
        require_once ABSPATH . 'wp-admin/includes/media.php';

        $overrides = [
            'test_form' => false,
        ];
        $uploaded = wp_handle_upload($file, $overrides);
        if (!is_array($uploaded) || !empty($uploaded['error']) || empty($uploaded['file'])) {
            $message = is_array($uploaded) ? (string) ($uploaded['error'] ?? '') : '';
            $this->append_runtime_log('error', 'Frontend upload failed', [
                'target' => $upload_target,
                'kind' => $upload_kind,
                'filename' => sanitize_file_name((string) $name),
                'reason' => $message,
            ]);
            wp_send_json_error(['message' => $message !== '' ? $message : __('Image upload failed.', 'nexdirectory')], 500);
        }

        $attachment = [
            'post_mime_type' => (string) ($uploaded['type'] ?? $mime),
            'post_title' => sanitize_text_field(pathinfo((string) $name, PATHINFO_FILENAME)),
            'post_content' => '',
            'post_status' => 'inherit',
            'post_author' => get_current_user_id(),
        ];
        $attachment_id = wp_insert_attachment($attachment, (string) $uploaded['file']);
        if (!$attachment_id || is_wp_error($attachment_id)) {
            $this->append_runtime_log('error', 'Frontend upload attachment insert failed', [
                'target' => $upload_target,
                'kind' => $upload_kind,
                'filename' => sanitize_file_name((string) $name),
                'error' => is_wp_error($attachment_id) ? $attachment_id->get_error_message() : '',
            ]);
            wp_send_json_error(['message' => __('Could not store uploaded image.', 'nexdirectory')], 500);
        }

        // Skip frontend image sub-size generation to avoid server memory/time failures.

        $url = wp_get_attachment_url((int) $attachment_id);
        if (!is_string($url) || $url === '') {
            $this->append_runtime_log('error', 'Frontend upload missing attachment URL', [
                'attachment_id' => (string) (int) $attachment_id,
                'filename' => sanitize_file_name((string) $name),
            ]);
            wp_send_json_error(['message' => __('Image uploaded but URL was unavailable.', 'nexdirectory')], 500);
        }

        wp_send_json_success([
            'id' => (int) $attachment_id,
            'url' => esc_url_raw($url),
            'kind' => $upload_kind,
            'name' => sanitize_file_name((string) $name),
        ]);
    }


    private function normalize_video_urls_textarea($raw) {
        $rows = preg_split('/\r\n|\r|\n/', (string) $raw);
        if (!is_array($rows)) {
            return '';
        }

        $normalized = [];
        foreach ($rows as $row) {
            $value = trim((string) $row);
            if ($value === '') {
                continue;
            }
            if (preg_match('/^[A-Za-z0-9_-]{11}$/', $value)) {
                $normalized[] = 'https://www.youtube.com/watch?v=' . $value;
                continue;
            }
            $url = esc_url_raw($value);
            if ($url !== '') {
                $normalized[] = $url;
            }
        }

        return implode("\n", array_values(array_unique($normalized)));
    }


    private function collect_uploaded_gallery_image_urls_from_request($field_name, $max_files_override = 0, $max_bytes_override = 0) {
        $field_name = sanitize_key((string) $field_name);
        if ($field_name === '' || empty($_FILES[$field_name]) || !is_array($_FILES[$field_name])) {
            return [];
        }

        $files = $_FILES[$field_name];
        if (isset($files['name']) && !is_array($files['name'])) {
            $files = [
                'name' => [$files['name']],
                'tmp_name' => [isset($files['tmp_name']) ? $files['tmp_name'] : ''],
                'error' => [isset($files['error']) ? $files['error'] : UPLOAD_ERR_NO_FILE],
                'size' => [isset($files['size']) ? $files['size'] : 0],
                'type' => [isset($files['type']) ? $files['type'] : ''],
            ];
        }
        $names = isset($files['name']) && is_array($files['name']) ? $files['name'] : [];
        $tmp_names = isset($files['tmp_name']) && is_array($files['tmp_name']) ? $files['tmp_name'] : [];
        $errors = isset($files['error']) && is_array($files['error']) ? $files['error'] : [];
        $sizes = isset($files['size']) && is_array($files['size']) ? $files['size'] : [];
        $types = isset($files['type']) && is_array($files['type']) ? $files['type'] : [];
        if (empty($names)) {
            return [];
        }
        $limits = $this->get_upload_limits();
        $max_bytes = (int) ($limits['gallery_max_bytes'] ?? 2048000);
        $max_files = (int) ($limits['gallery_max_files'] ?? 10);
        if ((int) $max_bytes_override > 0) {
            $max_bytes = (int) $max_bytes_override;
        }
        if ((int) $max_files_override > 0) {
            $max_files = (int) $max_files_override;
        }
        if ($max_files < 1) {
            $max_files = 10;
        }

        require_once ABSPATH . 'wp-admin/includes/file.php';
        require_once ABSPATH . 'wp-admin/includes/image.php';
        require_once ABSPATH . 'wp-admin/includes/media.php';

        $urls = [];
        foreach ($names as $index => $name) {
            if (count($urls) >= $max_files) {
                break;
            }
            $error = isset($errors[$index]) ? (int) $errors[$index] : UPLOAD_ERR_NO_FILE;
            if ($error === UPLOAD_ERR_NO_FILE) {
                continue;
            }
            if ($error !== UPLOAD_ERR_OK) {
                continue;
            }

            $tmp_name = isset($tmp_names[$index]) ? (string) $tmp_names[$index] : '';
            $file_name = isset($names[$index]) ? (string) $names[$index] : '';
            $size = isset($sizes[$index]) ? (int) $sizes[$index] : 0;
            if ($tmp_name === '' || $file_name === '' || $size <= 0 || $size > $max_bytes) {
                continue;
            }

            $check = wp_check_filetype_and_ext($tmp_name, $file_name);
            $mime = (string) ($check['type'] ?? '');
            if ($mime === '' || strpos($mime, 'image/') !== 0) {
                continue;
            }

            $single_file = [
                'name' => $file_name,
                'type' => isset($types[$index]) ? (string) $types[$index] : $mime,
                'tmp_name' => $tmp_name,
                'error' => $error,
                'size' => $size,
            ];
            $uploaded = wp_handle_upload($single_file, ['test_form' => false]);
            if (!is_array($uploaded) || !empty($uploaded['error']) || empty($uploaded['file'])) {
                continue;
            }

            $attachment = [
                'post_mime_type' => (string) ($uploaded['type'] ?? $mime),
                'post_title' => eep_sanitize_text(pathinfo($file_name, PATHINFO_FILENAME)),
                'post_content' => '',
                'post_status' => 'inherit',
                'post_author' => get_current_user_id(),
            ];
            $attachment_id = wp_insert_attachment($attachment, (string) $uploaded['file']);
            if (!$attachment_id || is_wp_error($attachment_id)) {
                continue;
            }

            // Skip frontend image sub-size generation to avoid server memory/time failures.

            $url = wp_get_attachment_url((int) $attachment_id);
            if (is_string($url) && $url !== '') {
                $urls[] = esc_url_raw($url);
            }
        }

        return array_values(array_unique($urls));
    }


    private function collect_uploaded_asset_urls_from_request($field_name, $max_files_override = 0, $max_bytes_override = 0) {
        $field_name = sanitize_key((string) $field_name);
        if ($field_name === '' || empty($_FILES[$field_name]) || !is_array($_FILES[$field_name])) {
            return [];
        }

        $files = $_FILES[$field_name];
        if (isset($files['name']) && !is_array($files['name'])) {
            $files = [
                'name' => [$files['name']],
                'tmp_name' => [isset($files['tmp_name']) ? $files['tmp_name'] : ''],
                'error' => [isset($files['error']) ? $files['error'] : UPLOAD_ERR_NO_FILE],
                'size' => [isset($files['size']) ? $files['size'] : 0],
                'type' => [isset($files['type']) ? $files['type'] : ''],
            ];
        }

        $names = isset($files['name']) && is_array($files['name']) ? $files['name'] : [];
        $tmp_names = isset($files['tmp_name']) && is_array($files['tmp_name']) ? $files['tmp_name'] : [];
        $errors = isset($files['error']) && is_array($files['error']) ? $files['error'] : [];
        $sizes = isset($files['size']) && is_array($files['size']) ? $files['size'] : [];
        $types = isset($files['type']) && is_array($files['type']) ? $files['type'] : [];
        if (empty($names)) {
            return [];
        }

        $limits = $this->get_upload_limits();
        $max_files = (int) ($max_files_override > 0 ? $max_files_override : 10);
        if ($max_files < 1) {
            $max_files = 10;
        }
        $max_bytes = (int) ($max_bytes_override > 0 ? $max_bytes_override : ($limits['file_max_bytes'] ?? 2097152));

        require_once ABSPATH . 'wp-admin/includes/file.php';
        require_once ABSPATH . 'wp-admin/includes/image.php';
        require_once ABSPATH . 'wp-admin/includes/media.php';

        $allowed_mimes = get_allowed_mime_types();
        $urls = [];
        foreach ($names as $index => $name) {
            if (count($urls) >= $max_files) {
                break;
            }
            $error = isset($errors[$index]) ? (int) $errors[$index] : UPLOAD_ERR_NO_FILE;
            if ($error === UPLOAD_ERR_NO_FILE || $error !== UPLOAD_ERR_OK) {
                continue;
            }

            $tmp_name = isset($tmp_names[$index]) ? (string) $tmp_names[$index] : '';
            $file_name = isset($names[$index]) ? (string) $names[$index] : '';
            $size = isset($sizes[$index]) ? (int) $sizes[$index] : 0;
            if ($tmp_name === '' || $file_name === '' || $size <= 0 || ($max_bytes > 0 && $size > $max_bytes)) {
                continue;
            }

            $check = wp_check_filetype_and_ext($tmp_name, $file_name, $allowed_mimes);
            $mime = (string) ($check['type'] ?? '');
            if ($mime === '') {
                $fallback = wp_check_filetype($file_name, $allowed_mimes);
                $mime = (string) ($fallback['type'] ?? '');
            }
            if ($mime === '') {
                continue;
            }

            $single_file = [
                'name' => $file_name,
                'type' => isset($types[$index]) ? (string) $types[$index] : $mime,
                'tmp_name' => $tmp_name,
                'error' => $error,
                'size' => $size,
            ];
            $uploaded = wp_handle_upload($single_file, ['test_form' => false]);
            if (!is_array($uploaded) || !empty($uploaded['error']) || empty($uploaded['file'])) {
                continue;
            }

            $attachment = [
                'post_mime_type' => (string) ($uploaded['type'] ?? $mime),
                'post_title' => eep_sanitize_text(pathinfo($file_name, PATHINFO_FILENAME)),
                'post_content' => '',
                'post_status' => 'inherit',
                'post_author' => get_current_user_id(),
            ];
            $attachment_id = wp_insert_attachment($attachment, (string) $uploaded['file']);
            if (!$attachment_id || is_wp_error($attachment_id)) {
                continue;
            }

            $url = wp_get_attachment_url((int) $attachment_id);
            if (is_string($url) && $url !== '') {
                $urls[] = esc_url_raw($url);
            }
        }

        return array_values(array_unique($urls));
    }


    private function normalize_event_webapp_request_data($current_user_id = 0) {
        $current_user_id = absint((int) $current_user_id);
        if ($current_user_id <= 0) {
            $current_user_id = (int) get_current_user_id();
        }

        foreach ([
            'eep_webapp_enabled',
            'eep_webapp_push_enabled',
            'eep_webapp_requires_login',
            'eep_webapp_qa_allow_attendee_answers',
            'eep_webapp_qa_allow_anonymous',
            'eep_webapp_enable_chat',
            'eep_webapp_enable_qa',
            'eep_webapp_enable_polls',
            'eep_webapp_enable_map',
            'eep_webapp_enable_notifications',
            'eep_webapp_enable_attendees',
            'eep_webapp_enable_custom_itinerary',
        ] as $webapp_toggle_key) {
            $_POST[$webapp_toggle_key] = (string) wp_unslash($_POST[$webapp_toggle_key] ?? '0') === '1' ? '1' : '0';
        }

        $_POST['eep_webapp_title'] = eep_sanitize_text((string) wp_unslash($_POST['eep_webapp_title'] ?? ''));
        $_POST['eep_webapp_subtitle'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_webapp_subtitle'] ?? ''));
        $_POST['eep_webapp_push_key'] = eep_sanitize_text((string) wp_unslash($_POST['eep_webapp_push_key'] ?? ''));
        $_POST['eep_webapp_push_provider'] = sanitize_key((string) wp_unslash($_POST['eep_webapp_push_provider'] ?? 'none'));
        if (!in_array((string) $_POST['eep_webapp_push_provider'], ['none', 'onesignal', 'firebase', 'custom'], true)) {
            $_POST['eep_webapp_push_provider'] = 'none';
        }

        $webapp_allowed_ids = preg_split('/[\s,]+/', (string) wp_unslash($_POST['eep_webapp_allowed_user_ids'] ?? '')) ?: [];
        $_POST['eep_webapp_allowed_user_ids'] = implode(',', array_values(array_unique(array_filter(array_map('absint', $webapp_allowed_ids)))));

        $_POST['eep_webapp_chat_moderation'] = sanitize_key((string) wp_unslash($_POST['eep_webapp_chat_moderation'] ?? 'open'));
        if (!in_array((string) $_POST['eep_webapp_chat_moderation'], ['open', 'moderated', 'admins-only'], true)) {
            $_POST['eep_webapp_chat_moderation'] = 'open';
        }
        $_POST['eep_webapp_qa_moderation'] = sanitize_key((string) wp_unslash($_POST['eep_webapp_qa_moderation'] ?? 'open'));
        if (!in_array((string) $_POST['eep_webapp_qa_moderation'], ['open', 'moderated', 'admins-only'], true)) {
            $_POST['eep_webapp_qa_moderation'] = 'open';
        }
        $_POST['eep_webapp_polls_vote_mode'] = sanitize_key((string) wp_unslash($_POST['eep_webapp_polls_vote_mode'] ?? 'single'));
        if (!in_array((string) $_POST['eep_webapp_polls_vote_mode'], ['single', 'multiple'], true)) {
            $_POST['eep_webapp_polls_vote_mode'] = 'single';
        }

        $accent_color = sanitize_hex_color((string) wp_unslash($_POST['eep_webapp_accent_color'] ?? ''));
        if ($accent_color === null || $accent_color === '') {
            $accent_color = sanitize_hex_color((string) get_option('eep_style_accent_color', '#4F46E5')) ?: '#4F46E5';
        }
        $_POST['eep_webapp_accent_color'] = $accent_color;

        $loading_bg_color = sanitize_hex_color((string) wp_unslash($_POST['eep_webapp_loading_bg_color'] ?? ''));
        if ($loading_bg_color === null || $loading_bg_color === '') {
            $loading_bg_color = '#ffffff';
        }
        $_POST['eep_webapp_loading_bg_color'] = $loading_bg_color;

        $limits = $this->get_upload_limits();
        $icon_uploaded = $this->collect_uploaded_gallery_image_urls_from_request(
            'eep_webapp_icon_file',
            1,
            (int) ($limits['cover_max_bytes'] ?? 512000)
        );
        $icon_url = esc_url_raw((string) wp_unslash($_POST['eep_webapp_icon_url'] ?? ''));
        if ($icon_url === '' && !empty($icon_uploaded[0])) {
            $icon_url = esc_url_raw((string) $icon_uploaded[0]);
        }
        if ($icon_url !== '' && !$this->can_current_user_use_media_url($icon_url, $current_user_id)) {
            $icon_url = '';
        }
        $_POST['eep_webapp_icon_url'] = $icon_url;

        $splash_uploaded = $this->collect_uploaded_gallery_image_urls_from_request(
            'eep_webapp_splash_file',
            1,
            (int) ($limits['gallery_max_bytes'] ?? 2048000)
        );
        $splash_url = esc_url_raw((string) wp_unslash($_POST['eep_webapp_splash_url'] ?? ''));
        if ($splash_url === '' && !empty($splash_uploaded[0])) {
            $splash_url = esc_url_raw((string) $splash_uploaded[0]);
        }
        if ($splash_url !== '' && !$this->can_current_user_use_media_url($splash_url, $current_user_id)) {
            $splash_url = '';
        }
        $_POST['eep_webapp_splash_url'] = $splash_url;

        $header_logo_uploaded = $this->collect_uploaded_gallery_image_urls_from_request(
            'eep_webapp_header_logo_file',
            1,
            (int) ($limits['cover_max_bytes'] ?? 512000)
        );
        $header_logo_url = esc_url_raw((string) wp_unslash($_POST['eep_webapp_header_logo_url'] ?? ''));
        if ($header_logo_url === '' && !empty($header_logo_uploaded[0])) {
            $header_logo_url = esc_url_raw((string) $header_logo_uploaded[0]);
        }
        if ($header_logo_url !== '' && !$this->can_current_user_use_media_url($header_logo_url, $current_user_id)) {
            $header_logo_url = '';
        }
        $_POST['eep_webapp_header_logo_url'] = $header_logo_url;

        $asset_urls_input = $this->parse_multiline_urls((string) wp_unslash($_POST['eep_webapp_asset_urls'] ?? ''));
        $asset_urls_uploaded = $this->collect_uploaded_asset_urls_from_request(
            'eep_webapp_asset_files',
            20,
            (int) ($limits['file_max_bytes'] ?? 2097152)
        );
        $asset_urls = array_values(array_unique(array_merge($asset_urls_input, $asset_urls_uploaded)));
        $asset_urls = $this->filter_media_urls_for_user($asset_urls, $current_user_id);
        $_POST['eep_webapp_asset_urls'] = implode("\n", $asset_urls);
    }


    private function get_event_webapp_meta_keys() {
        return [
            'eep_webapp_enabled',
            'eep_webapp_title',
            'eep_webapp_subtitle',
            'eep_webapp_icon_url',
            'eep_webapp_splash_url',
            'eep_webapp_header_logo_url',
            'eep_webapp_loading_bg_color',
            'eep_webapp_accent_color',
            'eep_webapp_push_enabled',
            'eep_webapp_push_provider',
            'eep_webapp_push_key',
            'eep_webapp_requires_login',
            'eep_webapp_allowed_user_ids',
            'eep_webapp_chat_moderation',
            'eep_webapp_qa_moderation',
            'eep_webapp_qa_allow_attendee_answers',
            'eep_webapp_qa_allow_anonymous',
            'eep_webapp_polls_vote_mode',
            'eep_webapp_enable_chat',
            'eep_webapp_enable_qa',
            'eep_webapp_enable_polls',
            'eep_webapp_enable_map',
            'eep_webapp_enable_notifications',
            'eep_webapp_enable_attendees',
            'eep_webapp_enable_custom_itinerary',
            'eep_webapp_asset_urls',
        ];
    }


    private function request_contains_event_webapp_fields() {
        foreach ($this->get_event_webapp_meta_keys() as $key) {
            if (array_key_exists($key, $_POST)) {
                return true;
            }
        }

        foreach ([
            'eep_webapp_icon_file',
            'eep_webapp_splash_file',
            'eep_webapp_header_logo_file',
            'eep_webapp_asset_files',
        ] as $file_key) {
            if (!empty($_FILES[$file_key])) {
                return true;
            }
        }

        return false;
    }


    public function handle_update_event_webapp() {
        if (!is_user_logged_in()) {
            wp_die(__('You must be logged in.', 'nexdirectory'));
        }

        $post_id = isset($_POST['post_id']) ? absint((int) wp_unslash($_POST['post_id'])) : 0;
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            wp_die(__('Invalid event.', 'nexdirectory'));
        }

        $current_user_id = (int) get_current_user_id();
        if (!$this->event_webapp_user_can_manage_event($post_id, $current_user_id)) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }

        if (!isset($_POST['eep_update_event_webapp_nonce']) || !wp_verify_nonce(
            eep_sanitize_text(wp_unslash($_POST['eep_update_event_webapp_nonce'])),
            'eep_update_event_webapp_' . $post_id
        )) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }

        $profile_key = $this->resolve_profile_key_for_post($post_id);
        $my_events_url = $this->get_platform_page_url(
            'eep_page_my_events_url',
            $this->get_profile_route_slugs('my_items', $profile_key),
            $this->get_profile_route_default_url('my_items', $profile_key)
        );
        $fallback_redirect = eep_add_query_arg([
            'eep_tab' => 'listings',
            'listings_subtab' => 'webapp',
            'webapp_event' => $post_id,
        ], $my_events_url) . '#listings';
        $redirect_to = $this->get_safe_redirect_url(wp_unslash($_POST['redirect_to'] ?? ''), $fallback_redirect);

        $this->normalize_event_webapp_request_data($current_user_id);
        foreach ($this->get_event_webapp_meta_keys() as $meta_key) {
            if (!array_key_exists($meta_key, $_POST)) {
                continue;
            }
            $value = wp_unslash($_POST[$meta_key]);
            update_post_meta($post_id, $meta_key, $this->sanitize_meta_value($meta_key, $value));
        }

        $poll_action = sanitize_key((string) wp_unslash($_POST['eep_webapp_poll_action'] ?? ''));
        if ($poll_action !== '') {
            $poll_rows = $this->get_event_webapp_poll_rows($post_id);
            if ($poll_action === 'create') {
                $question = eep_sanitize_textarea((string) wp_unslash($_POST['eep_webapp_poll_question'] ?? ''));
                $question = trim((string) $question);
                $options_input = preg_split('/\r\n|\r|\n|,/', (string) wp_unslash($_POST['eep_webapp_poll_options'] ?? '')) ?: [];
                $options_clean = array_values(array_unique(array_filter(array_map(static function ($entry) {
                    $label = sanitize_text_field((string) $entry);
                    if (function_exists('mb_strlen')) {
                        if (mb_strlen($label) > 120) {
                            $label = (string) mb_substr($label, 0, 120);
                        }
                    } elseif (strlen($label) > 120) {
                        $label = (string) substr($label, 0, 120);
                    }
                    return trim((string) $label);
                }, $options_input))));
                if ($question !== '' && count($options_clean) >= 2) {
                    $poll_id = $this->event_webapp_generate_id('poll');
                    $creator = get_user_by('id', $current_user_id);
                    $creator_name = $creator instanceof WP_User
                        ? sanitize_text_field((string) ($creator->display_name ?: $creator->user_login))
                        : __('Event Team', 'nexdirectory');
                    $multiple = (string) wp_unslash($_POST['eep_webapp_poll_multiple'] ?? '0') === '1';
                    $option_rows = [];
                    foreach ($options_clean as $option_index => $option_label) {
                        $option_rows[] = [
                            'id' => sanitize_key($poll_id . '-opt-' . ((int) $option_index + 1)),
                            'label' => $option_label,
                            'voters' => [],
                        ];
                    }
                    $poll_rows[] = [
                        'id' => $poll_id,
                        'question' => $question,
                        'status' => 'open',
                        'multiple' => $multiple,
                        'options' => $option_rows,
                        'created_by_user_id' => $current_user_id,
                        'created_by_name' => $creator_name,
                        'created_at_gmt' => current_time('mysql', true),
                    ];
                }
            } elseif (in_array($poll_action, ['delete', 'open', 'close', 'hide', 'clear_votes'], true)) {
                $poll_id = sanitize_key((string) wp_unslash($_POST['eep_webapp_poll_id'] ?? ''));
                if ($poll_id !== '') {
                    if ($poll_action === 'delete') {
                        $poll_rows = array_values(array_filter($poll_rows, static function ($row) use ($poll_id) {
                            if (!is_array($row)) {
                                return false;
                            }
                            return sanitize_key((string) ($row['id'] ?? '')) !== $poll_id;
                        }));
                    } else {
                        foreach ($poll_rows as &$poll_row) {
                            if (!is_array($poll_row)) {
                                continue;
                            }
                            if (sanitize_key((string) ($poll_row['id'] ?? '')) !== $poll_id) {
                                continue;
                            }
                            if ($poll_action === 'open') {
                                $poll_row['status'] = 'open';
                            } elseif ($poll_action === 'close') {
                                $poll_row['status'] = 'closed';
                            } elseif ($poll_action === 'hide') {
                                $poll_row['status'] = 'hidden';
                            } elseif ($poll_action === 'clear_votes') {
                                $options = is_array($poll_row['options'] ?? null) ? (array) $poll_row['options'] : [];
                                foreach ($options as &$option_row) {
                                    if (!is_array($option_row)) {
                                        continue;
                                    }
                                    $option_row['voters'] = [];
                                }
                                unset($option_row);
                                $poll_row['options'] = $options;
                            }
                            break;
                        }
                        unset($poll_row);
                    }
                }
            }
            $this->update_event_webapp_store_rows($post_id, 'eep_webapp_polls_feed', $poll_rows, 240);
        }

        $send_test_push = (string) wp_unslash($_POST['eep_webapp_send_test_push'] ?? '0') === '1';
        if ($send_test_push) {
            $settings = $this->get_event_webapp_settings($post_id);
            $test_notification = [
                'id' => $this->event_webapp_generate_id('test'),
                'text' => __('This is a test push notification from your Event Web App settings.', 'nexdirectory'),
                'type' => 'info',
                'author_id' => $current_user_id,
                'author_name' => sanitize_text_field((string) wp_get_current_user()->display_name),
                'created_at_gmt' => current_time('mysql', true),
            ];
            $this->maybe_dispatch_event_webapp_push_notification($post_id, $settings, $test_notification, [
                'trigger' => 'settings_test_push',
                'user_id' => $current_user_id,
            ]);
        }

        $message = $send_test_push
            ? __('Event Web App settings saved. Test push dispatched.', 'nexdirectory')
            : __('Event Web App settings saved.', 'nexdirectory');
        $this->redirect_with_message($redirect_to, $message, 'success');
    }


    public function handle_update_event() {
        $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0;
        $profile_key = $this->resolve_profile_key_for_post($post_id);
        $ui_labels = $this->get_site_profile_ui_labels($profile_key);
        $profile_key = sanitize_key((string) ($ui_labels['profile_key'] ?? $profile_key));
        $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';
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));

        if (!is_user_logged_in()) {
            wp_die(__('You must be logged in.', 'nexdirectory'));
        }
        $current_user_id = (int) get_current_user_id();
        if (!$this->user_can_submit_events($current_user_id) && !$this->user_can_edit_event($post_id, $current_user_id)) {
            wp_die(sprintf(__('You do not have permission to edit %s.', 'nexdirectory'), strtolower($singular_label)));
        }

        if (!$this->user_can_edit_event($post_id, $current_user_id)) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }

        if (!isset($_POST['eep_update_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_update_nonce'])), 'eep_update_event_' . $post_id)) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }

        $this->hydrate_event_datetime_fields_from_request();
        $_POST['eep_timezone'] = $this->sanitize_timezone_value(wp_unslash($_POST['eep_timezone'] ?? ''));
        $_POST['eep_sessions'] = wp_unslash($_POST['eep_sessions'] ?? '[]');
        $_POST['eep_key_speakers'] = wp_unslash($_POST['eep_key_speakers'] ?? '[]');
        $_POST['eep_official_event'] = $this->normalize_official_event_value(wp_unslash($_POST['eep_official_event'] ?? '0'));
        $requested_official_value = (string) $_POST['eep_official_event'];
        $_POST['eep_lock_pin'] = (string) (wp_unslash($_POST['eep_lock_pin'] ?? '0')) === '1' ? '1' : '0';
        $_POST['eep_price_simple'] = sanitize_key((string) wp_unslash($_POST['eep_price_simple'] ?? 'free'));
        $_POST['eep_event_stream_url'] = esc_url_raw((string) wp_unslash($_POST['eep_event_stream_url'] ?? ''));
        $_POST['eep_meta_title'] = $this->sanitize_event_seo_title(wp_unslash($_POST['eep_meta_title'] ?? ''));
        $_POST['eep_meta_description'] = $this->sanitize_event_seo_description(wp_unslash($_POST['eep_meta_description'] ?? ''));
        if ((string) get_option('eep_enable_event_seo_fields', '1') !== '1') {
            unset($_POST['eep_meta_title'], $_POST['eep_meta_description']);
        }
        $_POST['eep_youtube_video_id_1'] = $this->normalize_submitted_youtube_id(wp_unslash($_POST['eep_youtube_video_id_1'] ?? ''));
        $_POST['eep_youtube_video_id_2'] = $this->normalize_submitted_youtube_id(wp_unslash($_POST['eep_youtube_video_id_2'] ?? ''));
        $_POST['eep_company_name'] = sanitize_text_field((string) wp_unslash($_POST['eep_company_name'] ?? ''));
        $_POST['eep_directory_business_type'] = sanitize_key((string) wp_unslash($_POST['eep_directory_business_type'] ?? 'independent-business'));
        $_POST['eep_directory_listing_status'] = sanitize_key((string) wp_unslash($_POST['eep_directory_listing_status'] ?? 'active'));
        $_POST['eep_directory_contact_person'] = sanitize_text_field((string) wp_unslash($_POST['eep_directory_contact_person'] ?? ''));
        $_POST['eep_directory_service_area'] = sanitize_text_field((string) wp_unslash($_POST['eep_directory_service_area'] ?? ''));
        $_POST['eep_directory_operating_hours'] = sanitize_text_field((string) wp_unslash($_POST['eep_directory_operating_hours'] ?? ''));
        $_POST['eep_directory_response_time'] = sanitize_text_field((string) wp_unslash($_POST['eep_directory_response_time'] ?? ''));
        $_POST['eep_directory_established_year'] = absint((int) wp_unslash($_POST['eep_directory_established_year'] ?? 0));
        $_POST['eep_directory_pricing_summary'] = sanitize_text_field((string) wp_unslash($_POST['eep_directory_pricing_summary'] ?? ''));
        $_POST['eep_directory_appointment_url'] = esc_url_raw((string) wp_unslash($_POST['eep_directory_appointment_url'] ?? ''));
        $_POST['eep_directory_whatsapp_url'] = esc_url_raw((string) wp_unslash($_POST['eep_directory_whatsapp_url'] ?? ''));
        $_POST['eep_directory_services'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_directory_services'] ?? ''));
        $_POST['eep_directory_highlights'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_directory_highlights'] ?? ''));
        $_POST['eep_directory_certifications'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_directory_certifications'] ?? ''));
        $_POST['eep_job_type'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_type'] ?? ''));
        $_POST['eep_job_work_mode'] = sanitize_key((string) wp_unslash($_POST['eep_job_work_mode'] ?? ''));
        $_POST['eep_job_posted_date'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_posted_date'] ?? ''));
        $_POST['eep_job_compensation_type'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_compensation_type'] ?? ''));
        $_POST['eep_job_contract_duration'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_contract_duration'] ?? ''));
        $_POST['eep_job_working_hours'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_working_hours'] ?? ''));
        $_POST['eep_job_salary'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_salary'] ?? ''));
        $_POST['eep_job_apply_url'] = esc_url_raw((string) wp_unslash($_POST['eep_job_apply_url'] ?? ''));
        $_POST['eep_job_application_email'] = sanitize_email((string) wp_unslash($_POST['eep_job_application_email'] ?? ''));
        $_POST['eep_job_closing_date'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_closing_date'] ?? ''));
        $_POST['eep_job_department'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_department'] ?? ''));
        $_POST['eep_job_experience_level'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_experience_level'] ?? ''));
        $_POST['eep_job_reporting_to'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_reporting_to'] ?? ''));
        $_POST['eep_job_remote_policy'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_remote_policy'] ?? ''));
        $_POST['eep_job_visa_sponsorship'] = ((string) wp_unslash($_POST['eep_job_visa_sponsorship'] ?? '0')) === '1' ? '1' : '0';
        $_POST['eep_job_open_positions'] = absint((int) wp_unslash($_POST['eep_job_open_positions'] ?? 0));
        $_POST['eep_job_interview_process'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_job_interview_process'] ?? ''));
        $_POST['eep_job_expected_start_date'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_expected_start_date'] ?? ''));
        $_POST['eep_job_skills'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_skills'] ?? ''));
        $_POST['eep_job_responsibilities'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_job_responsibilities'] ?? ''));
        $_POST['eep_job_requirements'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_job_requirements'] ?? ''));
        $_POST['eep_job_benefits'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_job_benefits'] ?? ''));
        $_POST['eep_job_equity'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_equity'] ?? ''));
        $_POST['eep_job_bonus'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_bonus'] ?? ''));
        $_POST['eep_job_recruitment_timeline'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_job_recruitment_timeline'] ?? ''));
        $_POST['eep_job_company_intro_video'] = esc_url_raw((string) wp_unslash($_POST['eep_job_company_intro_video'] ?? ''));
        $_POST['eep_job_role_overview_video'] = esc_url_raw((string) wp_unslash($_POST['eep_job_role_overview_video'] ?? ''));
        $_POST['eep_job_company_website'] = esc_url_raw((string) wp_unslash($_POST['eep_job_company_website'] ?? ''));
        $_POST['eep_job_company_size'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_company_size'] ?? ''));
        $_POST['eep_job_industry'] = sanitize_text_field((string) wp_unslash($_POST['eep_job_industry'] ?? ''));
        $_POST['eep_market_condition'] = sanitize_text_field((string) wp_unslash($_POST['eep_market_condition'] ?? ''));
        $_POST['eep_market_price_type'] = sanitize_key((string) wp_unslash($_POST['eep_market_price_type'] ?? 'fixed'));
        $_POST['eep_market_quantity'] = absint((int) wp_unslash($_POST['eep_market_quantity'] ?? 0));
        $_POST['eep_market_sku'] = sanitize_text_field((string) wp_unslash($_POST['eep_market_sku'] ?? ''));
        $_POST['eep_market_brand'] = sanitize_text_field((string) wp_unslash($_POST['eep_market_brand'] ?? ''));
        $_POST['eep_market_model'] = sanitize_text_field((string) wp_unslash($_POST['eep_market_model'] ?? ''));
        $_POST['eep_market_year'] = absint((int) wp_unslash($_POST['eep_market_year'] ?? 0));
        $_POST['eep_market_seller_type'] = sanitize_key((string) wp_unslash($_POST['eep_market_seller_type'] ?? 'private'));
        $_POST['eep_market_listing_status'] = sanitize_key((string) wp_unslash($_POST['eep_market_listing_status'] ?? 'active'));
        $_POST['eep_market_expires_on'] = sanitize_text_field((string) wp_unslash($_POST['eep_market_expires_on'] ?? ''));
        $_POST['eep_market_returns_accepted'] = ((string) wp_unslash($_POST['eep_market_returns_accepted'] ?? '0')) === '1' ? '1' : '0';
        $_POST['eep_market_accept_offers'] = ((string) wp_unslash($_POST['eep_market_accept_offers'] ?? '1')) === '0' ? '0' : '1';
        $_POST['eep_market_payment_methods'] = sanitize_text_field((string) wp_unslash($_POST['eep_market_payment_methods'] ?? ''));
        $_POST['eep_market_condition_notes'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_market_condition_notes'] ?? ''));
        $_POST['eep_market_shipping_notes'] = eep_sanitize_textarea((string) wp_unslash($_POST['eep_market_shipping_notes'] ?? ''));
        $update_market_delivery_raw = wp_unslash($_POST['eep_market_delivery_methods'] ?? []);
        if (!is_array($update_market_delivery_raw)) {
            $update_market_delivery_raw = preg_split('/[\s,]+/', (string) $update_market_delivery_raw);
            if (!is_array($update_market_delivery_raw)) {
                $update_market_delivery_raw = [];
            }
        }
        $allowed_market_delivery_methods = ['pickup', 'shipping', 'local-delivery', 'digital'];
        $update_market_delivery = array_values(array_unique(array_filter(array_map(static function ($method) use ($allowed_market_delivery_methods) {
            $normalized = sanitize_key((string) $method);
            return in_array($normalized, $allowed_market_delivery_methods, true) ? $normalized : '';
        }, $update_market_delivery_raw))));
        $_POST['eep_market_delivery_methods'] = implode(',', $update_market_delivery);
        $_POST['eep_listing_profile'] = $profile_key;
        $this->sync_event_price_with_ticket_request($is_events_profile);
        if ($is_job_profile && trim((string) wp_unslash($_POST['eep_external_url'] ?? '')) === '' && (string) $_POST['eep_job_company_website'] !== '') {
            $_POST['eep_external_url'] = (string) $_POST['eep_job_company_website'];
        }
        if ($_POST['eep_timezone'] === '') {
            $_POST['eep_timezone'] = $this->sanitize_timezone_value((string) get_post_meta($post_id, 'eep_timezone', true));
        }
        $intent = sanitize_key(wp_unslash($_POST['eep_intent'] ?? 'pending'));
        $is_draft_intent = in_array($intent, ['draft', 'preview'], true);
        $requires_complete_submission = !$is_draft_intent;

        $title = eep_sanitize_text(wp_unslash($_POST['post_title'] ?? ''));
        if ($title === '') {
            $title = $this->recover_existing_event_title_value($post_id, (string) get_the_title($post_id));
        }
        if (trim((string) ($_POST['eep_start'] ?? '')) === '') {
            $recovered_start = $this->recover_existing_event_datetime_value($post_id, 'eep_start');
            if ($recovered_start !== '') {
                $_POST['eep_start'] = $recovered_start;
            }
        }
        if (trim((string) ($_POST['eep_end'] ?? '')) === '') {
            $recovered_end = $this->recover_existing_event_datetime_value($post_id, 'eep_end');
            if ($recovered_end !== '') {
                $_POST['eep_end'] = $recovered_end;
            }
        }
        if ($requires_complete_submission && strlen($title) < 8) {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), sprintf(__('%s name should be at least 8 characters.', 'nexdirectory'), $singular_label), 'error');
        }
        $job_application_email_raw = trim((string) wp_unslash($_POST['eep_job_application_email'] ?? ''));
        $job_application_email = sanitize_email($job_application_email_raw);
        if ($job_application_email_raw !== '' && !is_email($job_application_email)) {
            if ($requires_complete_submission) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Please enter a valid application email or leave it blank.', 'nexdirectory'), 'error');
            }
            $job_application_email = '';
        }
        $_POST['eep_job_application_email'] = $job_application_email;
        $contact_email_raw = trim((string) wp_unslash($_POST['eep_contact_email'] ?? ''));
        $contact_email = sanitize_email($contact_email_raw);
        if ($contact_email_raw !== '' && !is_email($contact_email)) {
            if ($requires_complete_submission) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Please enter a valid contact email or leave it blank.', 'nexdirectory'), 'error');
            }
            $contact_email = '';
        }
        $_POST['eep_contact_email'] = $contact_email;
        if (!$is_events_profile) {
            $start_value = trim((string) ($_POST['eep_start'] ?? ''));
            $_POST['eep_recurrence'] = 'none';
            $_POST['eep_recurrence_until'] = '';
            $_POST['eep_sessions'] = '[]';
            $_POST['eep_key_speakers'] = '[]';
            if ($is_job_profile) {
                $job_posted_date = trim((string) ($_POST['eep_job_posted_date'] ?? ''));
                if ($job_posted_date !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $job_posted_date) && strtotime($job_posted_date . ' 00:00:00')) {
                    $_POST['eep_start'] = $job_posted_date . ' 09:00:00';
                } elseif ($start_value === '') {
                    $_POST['eep_start'] = current_time('Y-m-d H:i:s');
                }
                if ($job_posted_date === '' && !empty($_POST['eep_start']) && preg_match('/^\d{4}-\d{2}-\d{2}/', (string) $_POST['eep_start'], $posted_match)) {
                    $_POST['eep_job_posted_date'] = (string) ($posted_match[0] ?? '');
                }
                $job_closing = trim((string) ($_POST['eep_job_closing_date'] ?? ''));
                if ($job_closing !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $job_closing)) {
                    $_POST['eep_end'] = $job_closing . ' 23:59:00';
                } elseif (trim((string) ($_POST['eep_end'] ?? '')) === '') {
                    $_POST['eep_end'] = '';
                }
            } elseif ($is_marketplace_profile) {
                if ($start_value === '') {
                    $_POST['eep_start'] = current_time('Y-m-d H:i:s');
                }
                $market_expires_on = trim((string) ($_POST['eep_market_expires_on'] ?? ''));
                if ($market_expires_on !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $market_expires_on)) {
                    $_POST['eep_end'] = $market_expires_on . ' 23:59:00';
                } else {
                    $_POST['eep_end'] = '';
                }
            } else {
                if ($start_value === '') {
                    $_POST['eep_start'] = current_time('Y-m-d H:i:s');
                }
                $_POST['eep_end'] = '';
            }
        }
        if ($requires_complete_submission) {
            $time_check = $this->validate_event_times($_POST['eep_start'], $_POST['eep_end'], true);
            if (is_wp_error($time_check)) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), $time_check->get_error_message(), 'error');
            }
            if ($is_job_profile) {
            if (trim((string) ($_POST['eep_company_name'] ?? '')) === '') {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Company name is required for job listings.', 'nexdirectory'), 'error');
            }
            $job_posted = trim((string) ($_POST['eep_job_posted_date'] ?? ''));
            if ($job_posted !== '' && (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $job_posted) || !strtotime($job_posted . ' 00:00:00'))) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Posted date is invalid.', 'nexdirectory'), 'error');
            }
            if (trim((string) ($_POST['eep_job_type'] ?? '')) === '') {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Employment type is required for job listings.', 'nexdirectory'), 'error');
            }
            if (trim((string) ($_POST['eep_job_department'] ?? '')) === '') {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Department is required for job listings.', 'nexdirectory'), 'error');
            }
            if (!in_array((string) ($_POST['eep_job_work_mode'] ?? ''), ['on-site', 'hybrid', 'remote'], true)) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Work setup is required for job listings.', 'nexdirectory'), 'error');
            }
            if (trim((string) ($_POST['eep_job_compensation_type'] ?? '')) === '') {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Compensation type is required for job listings.', 'nexdirectory'), 'error');
            }
            if (trim((string) ($_POST['eep_job_salary'] ?? '')) === '') {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Salary range is required for job listings.', 'nexdirectory'), 'error');
            }
            if (trim((string) ($_POST['eep_job_apply_url'] ?? '')) === '' && trim((string) ($_POST['eep_job_application_email'] ?? '')) === '') {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Provide at least one application method (URL or email).', 'nexdirectory'), 'error');
            }
            $job_closing = trim((string) ($_POST['eep_job_closing_date'] ?? ''));
            if ($job_closing === '') {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Closing date is required for job listings.', 'nexdirectory'), 'error');
            }
            if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $job_closing) || !strtotime($job_closing . ' 00:00:00')) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Closing date is invalid.', 'nexdirectory'), 'error');
            }
        } elseif ($is_directory_profile) {
            if (trim((string) ($_POST['eep_company_name'] ?? '')) === '') {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Business name is required for directory listings.', 'nexdirectory'), 'error');
            }
            if (!in_array((string) ($_POST['eep_directory_business_type'] ?? ''), ['independent-business', 'agency', 'private-owner', 'nonprofit', 'multi-location', 'franchise'], true)) {
                $_POST['eep_directory_business_type'] = 'independent-business';
            }
            if (!in_array((string) ($_POST['eep_directory_listing_status'] ?? ''), ['active', 'temporarily-closed', 'permanently-closed'], true)) {
                $_POST['eep_directory_listing_status'] = 'active';
            }
            $has_contact_method = (
                trim((string) ($_POST['eep_contact_phone'] ?? '')) !== ''
                || trim((string) ($_POST['eep_contact_email'] ?? '')) !== ''
                || trim((string) ($_POST['eep_external_url'] ?? '')) !== ''
                || trim((string) ($_POST['eep_directory_appointment_url'] ?? '')) !== ''
                || trim((string) ($_POST['eep_directory_whatsapp_url'] ?? '')) !== ''
            );
            if (!$has_contact_method) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Add at least one owner contact method (email, phone, website, booking URL, or WhatsApp).', 'nexdirectory'), 'error');
            }
        } elseif ($is_marketplace_profile) {
            if (trim((string) ($_POST['eep_market_condition'] ?? '')) === '') {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Condition is required for marketplace listings.', 'nexdirectory'), 'error');
            }
            $market_price_type = sanitize_key((string) ($_POST['eep_market_price_type'] ?? 'fixed'));
            if (!in_array($market_price_type, ['fixed', 'negotiable', 'auction', 'free'], true)) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Price type is invalid for marketplace listings.', 'nexdirectory'), 'error');
            }
            if ($market_price_type !== 'free' && trim((string) ($_POST['eep_price'] ?? '')) === '') {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Asking price is required unless this listing is marked as free.', 'nexdirectory'), 'error');
            }
            if ($market_price_type === 'free' && trim((string) ($_POST['eep_price'] ?? '')) === '') {
                $_POST['eep_price'] = __('Free', 'nexdirectory');
            }
            $market_listing_status = sanitize_key((string) ($_POST['eep_market_listing_status'] ?? 'active'));
            if (!in_array($market_listing_status, ['active', 'reserved', 'sold', 'expired'], true)) {
                $_POST['eep_market_listing_status'] = 'active';
            }
            $market_seller_type = sanitize_key((string) ($_POST['eep_market_seller_type'] ?? 'private'));
            if (!in_array($market_seller_type, ['private', 'business'], true)) {
                $_POST['eep_market_seller_type'] = 'private';
            }
            $market_expires_on = trim((string) ($_POST['eep_market_expires_on'] ?? ''));
            if ($market_expires_on !== '' && (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $market_expires_on) || !strtotime($market_expires_on . ' 00:00:00'))) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Expiry date is invalid for marketplace listings.', 'nexdirectory'), 'error');
            }
            $market_delivery_selected = array_filter(array_map('sanitize_key', preg_split('/[\s,]+/', (string) ($_POST['eep_market_delivery_methods'] ?? '')) ?: []));
            if (empty($market_delivery_selected)) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Select at least one delivery method for marketplace listings.', 'nexdirectory'), 'error');
            }
            $has_contact_method = (
                trim((string) ($_POST['eep_contact_phone'] ?? '')) !== ''
                || trim((string) ($_POST['eep_contact_email'] ?? '')) !== ''
                || trim((string) ($_POST['eep_external_url'] ?? '')) !== ''
            );
            if (!$has_contact_method) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Add at least one seller contact method (email, phone, or external listing URL).', 'nexdirectory'), 'error');
            }
        }
        }
        $this->maybe_fill_request_coordinates_from_location_fields();
        $parsed_tags = $this->parse_event_tags_input(wp_unslash($_POST['event_tags'] ?? ''), 8, 40);
        if (is_wp_error($parsed_tags)) {
            if ($requires_complete_submission) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), $parsed_tags->get_error_message(), 'error');
            }
            $parsed_tags = [];
        }
        $excerpt = eep_sanitize_textarea(wp_unslash($_POST['post_excerpt'] ?? ''));
        if (!array_key_exists('post_excerpt', $_POST)) {
            $excerpt = (string) get_post_field('post_excerpt', $post_id);
        }
        $post_content = wp_kses_post(wp_unslash($_POST['post_content'] ?? ''));
        if (!array_key_exists('post_content', $_POST)) {
            $post_content = (string) get_post_field('post_content', $post_id);
        }
        $excerpt_max_chars = max(80, min(1000, (int) get_option('eep_excerpt_max_chars', 220)));
        $excerpt_length = function_exists('mb_strlen') ? mb_strlen($excerpt) : strlen($excerpt);
        if ($excerpt_length > $excerpt_max_chars) {
            $this->redirect_with_message(
                wp_get_referer() ?: home_url('/'),
                sprintf(__('Short excerpt must be %d characters or fewer.', 'nexdirectory'), $excerpt_max_chars),
                'error'
            );
        }
        $existing_status = (string) get_post_status($post_id);
        $edit_policy = sanitize_key((string) get_option('eep_user_edit_policy', 'require_approval'));
        if (!in_array($edit_policy, ['allow', 'require_approval', 'disallow'], true)) {
            $edit_policy = 'require_approval';
        }
        $auto_approve_after_first = (string) get_option('eep_auto_approve_edits_after_first_approval', '0') === '1';
        if (
            $edit_policy === 'require_approval'
            && $auto_approve_after_first
            && $existing_status === 'publish'
            && $this->user_has_published_listings(get_current_user_id())
        ) {
            $edit_policy = 'allow';
        }
        if ($this->user_can_bypass_edit_approval(get_current_user_id())) {
            $edit_policy = 'allow';
        }
        if ($edit_policy === 'disallow' && $existing_status === 'publish') {
            $this->redirect_with_message(
                wp_get_referer() ?: home_url('/'),
                sprintf(__('You cannot edit published %s. Contact an admin for updates.', 'nexdirectory'), strtolower($singular_label)),
                'error'
            );
        }
        $target_status = 'pending';
        if ($is_draft_intent) {
            $target_status = 'draft';
        } elseif ($edit_policy === 'allow') {
            $target_status = in_array($existing_status, ['publish', 'pending', 'draft'], true) ? $existing_status : 'pending';
        } elseif ($edit_policy === 'disallow') {
            $target_status = in_array($existing_status, ['pending', 'draft'], true) ? $existing_status : 'pending';
        }

        wp_update_post([
            'ID' => $post_id,
            'post_title' => $title,
            'post_content' => $post_content,
            'post_excerpt' => $excerpt,
            'post_status' => $target_status,
        ]);
        $this->ensure_event_owner_team_member((int) $post_id);

        $upload_limits = $this->get_upload_limits();
        $cover_uploaded_urls = $this->collect_uploaded_gallery_image_urls_from_request(
            'eep_cover_file',
            1,
            (int) ($upload_limits['cover_max_bytes'] ?? 512000)
        );
        $cover_input_url = esc_url_raw((string) wp_unslash($_POST['eep_cover_image_url'] ?? ''));
        if ($cover_input_url === '' && !empty($cover_uploaded_urls[0])) {
            $cover_input_url = esc_url_raw((string) $cover_uploaded_urls[0]);
        }
        if ($cover_input_url !== '' && !$this->can_current_user_use_media_url($cover_input_url, get_current_user_id())) {
            $cover_input_url = '';
        }

        $gallery_urls_input = $this->parse_multiline_urls((string) wp_unslash($_POST['eep_gallery_urls'] ?? ''));
        $gallery_urls_uploaded = $this->collect_uploaded_gallery_image_urls_from_request('eep_gallery_files');
        $gallery_urls_merged = array_values(array_unique(array_merge($gallery_urls_input, $gallery_urls_uploaded)));
        $gallery_urls_merged = $this->filter_media_urls_for_user($gallery_urls_merged, get_current_user_id());
        $normalized_media = $this->normalize_cover_and_gallery_meta_urls($cover_input_url, implode("\n", $gallery_urls_merged));
        $_POST['eep_cover_image_url'] = (string) ($normalized_media['cover'] ?? '');
        $_POST['eep_gallery_urls'] = implode("\n", (array) ($normalized_media['gallery'] ?? []));
        if ($is_events_profile && $this->request_contains_event_webapp_fields()) {
            $this->normalize_event_webapp_request_data($current_user_id);
        }

        foreach ($this->get_event_meta_fields() as $meta_key => $meta_type) {
            if (!array_key_exists($meta_key, $_POST)) {
                continue;
            }
            $value = wp_unslash($_POST[$meta_key]);
            if ($this->should_skip_sparse_meta_update($post_id, $meta_key, $value, true)) {
                continue;
            }
            update_post_meta($post_id, $meta_key, $this->sanitize_meta_value($meta_key, $value));
        }
        $this->persist_listing_profile_for_post($post_id, $profile_key);
        $resolved_official_value = $this->apply_official_flag_with_admin_gate($post_id, $requested_official_value, get_current_user_id());
        $_POST['eep_official_event'] = $resolved_official_value;

        $video_urls_raw = $this->normalize_video_urls_textarea(wp_unslash($_POST['eep_video_urls'] ?? ''));
        $gallery_urls_raw = (string) ($_POST['eep_gallery_urls'] ?? '');

        $duplicate_candidate_id = 0;
        if ((string) get_option('eep_enable_duplicate_detection', '1') === '1') {
            $duplicate_candidate_id = $this->find_duplicate_event_candidate(
                $title,
                wp_unslash($_POST['eep_start'] ?? ''),
                wp_unslash($_POST['eep_city'] ?? ''),
                wp_unslash($_POST['eep_address'] ?? ''),
                wp_unslash($_POST['eep_venue'] ?? ''),
                $post_id
            );
        }
        if ($duplicate_candidate_id > 0) {
            update_post_meta($post_id, 'eep_duplicate_candidate', (string) $duplicate_candidate_id);
        } else {
            delete_post_meta($post_id, 'eep_duplicate_candidate');
        }

        $this->update_media_meta(
            $post_id,
            $video_urls_raw,
            $gallery_urls_raw
        );
        if ((string) get_option('eep_enable_custom_submission_fields', '0') === '1' && isset($_POST['eep_custom_fields_builder_json'])) {
            $custom_payload = wp_unslash($_POST['eep_custom_fields_builder_json'] ?? '[]');
            $custom_parsed = $this->parse_custom_field_builder_payload(is_string($custom_payload) ? $custom_payload : '[]');
            $custom_form_fields = is_array($custom_parsed['fields'] ?? null) ? (array) $custom_parsed['fields'] : [];
            $custom_field_values = is_array($custom_parsed['values'] ?? null) ? (array) $custom_parsed['values'] : [];
            if (!empty($custom_form_fields)) {
                update_post_meta($post_id, 'eep_custom_fields_schema', wp_json_encode($custom_form_fields));
            } else {
                delete_post_meta($post_id, 'eep_custom_fields_schema');
            }
            $this->update_post_custom_field_values($post_id, $custom_field_values, $custom_form_fields);
        }
        update_post_meta($post_id, 'eep_sessions', wp_json_encode($this->parse_sessions($_POST['eep_sessions'] ?? '[]')));
        update_post_meta($post_id, 'eep_key_speakers', wp_json_encode($this->parse_key_speakers($_POST['eep_key_speakers'] ?? '[]')));
        if (!empty($_POST['event_type']) && intval($_POST['event_type']) > 0) {
            wp_set_post_terms($post_id, [intval($_POST['event_type'])], self::TAX_TYPE, false);
        }
        if ($is_job_profile) {
            $job_type_terms = [];
            $job_department_label = trim((string) ($_POST['eep_job_department'] ?? ''));
            $job_type_label = trim((string) ($_POST['eep_job_type'] ?? ''));
            foreach ([$job_department_label, $job_type_label] as $candidate_term) {
                if ($candidate_term === '') {
                    continue;
                }
                $candidate_term_id = $this->get_or_create_term_id(self::TAX_TYPE, $candidate_term);
                if ($candidate_term_id > 0) {
                    $job_type_terms[] = (int) $candidate_term_id;
                }
            }
            if (!empty($job_type_terms)) {
                wp_set_post_terms($post_id, array_values(array_unique($job_type_terms)), self::TAX_TYPE, false);
            }
            $work_mode = sanitize_key((string) ($_POST['eep_job_work_mode'] ?? ''));
            $work_mode_label = '';
            if ($work_mode === 'remote') {
                $work_mode_label = __('Remote', 'nexdirectory');
            } elseif ($work_mode === 'hybrid') {
                $work_mode_label = __('Hybrid', 'nexdirectory');
            } elseif ($work_mode === 'on-site') {
                $work_mode_label = __('On-site', 'nexdirectory');
            }
            if ($work_mode_label !== '') {
                $work_mode_term_id = $this->get_or_create_term_id(self::TAX_MODE, $work_mode_label);
                if ($work_mode_term_id > 0) {
                    wp_set_post_terms($post_id, [$work_mode_term_id], self::TAX_MODE, false);
                }
            }
            wp_set_post_terms($post_id, [], self::TAX_PRICE, false);
        } elseif ($is_marketplace_profile) {
            $delivery_methods = array_filter(array_map('sanitize_key', preg_split('/[\s,]+/', (string) ($_POST['eep_market_delivery_methods'] ?? '')) ?: []));
            $delivery_methods = array_values(array_unique(array_filter($delivery_methods, static function ($method) {
                return in_array($method, ['pickup', 'shipping', 'local-delivery', 'digital'], true);
            })));
            $delivery_labels = [
                'pickup' => __('Pickup', 'nexdirectory'),
                'shipping' => __('Shipping', 'nexdirectory'),
                'local-delivery' => __('Local Delivery', 'nexdirectory'),
                'digital' => __('Digital Delivery', 'nexdirectory'),
            ];
            $mode_term_ids = [];
            foreach ($delivery_methods as $delivery_method) {
                $term_name = (string) ($delivery_labels[$delivery_method] ?? '');
                if ($term_name === '') {
                    continue;
                }
                $term_id = $this->get_or_create_term_id(self::TAX_MODE, $term_name);
                if ($term_id > 0) {
                    $mode_term_ids[] = (int) $term_id;
                }
            }
            if (!empty($mode_term_ids)) {
                wp_set_post_terms($post_id, array_values(array_unique($mode_term_ids)), self::TAX_MODE, false);
            } else {
                wp_set_post_terms($post_id, [], self::TAX_MODE, false);
            }

            $market_price_type = sanitize_key((string) ($_POST['eep_market_price_type'] ?? 'fixed'));
            $price_term_map = [
                'free' => __('Free', 'nexdirectory'),
                'fixed' => __('Fixed Price', 'nexdirectory'),
                'negotiable' => __('Negotiable', 'nexdirectory'),
                'auction' => __('Auction', 'nexdirectory'),
            ];
            $price_term_name = (string) ($price_term_map[$market_price_type] ?? $price_term_map['fixed']);
            $price_term_id = $this->get_or_create_term_id(self::TAX_PRICE, $price_term_name);
            if ($price_term_id > 0) {
                wp_set_post_terms($post_id, [$price_term_id], self::TAX_PRICE, false);
            } else {
                wp_set_post_terms($post_id, [], self::TAX_PRICE, false);
            }
        } elseif ($is_directory_profile) {
            $directory_status = sanitize_key((string) ($_POST['eep_directory_listing_status'] ?? 'active'));
            $status_term_map = [
                'active' => __('Active', 'nexdirectory'),
                'temporarily-closed' => __('Temporarily Closed', 'nexdirectory'),
                'permanently-closed' => __('Permanently Closed', 'nexdirectory'),
            ];
            $status_term_name = (string) ($status_term_map[$directory_status] ?? $status_term_map['active']);
            $status_term_id = $this->get_or_create_term_id(self::TAX_MODE, $status_term_name);
            if ($status_term_id > 0) {
                wp_set_post_terms($post_id, [$status_term_id], self::TAX_MODE, false);
            } else {
                wp_set_post_terms($post_id, [], self::TAX_MODE, false);
            }

            wp_set_post_terms($post_id, [], self::TAX_PRICE, false);

            $directory_type = sanitize_key((string) ($_POST['eep_directory_business_type'] ?? 'independent-business'));
            $directory_type_term_map = [
                'independent-business' => __('Independent Business', 'nexdirectory'),
                'agency' => __('Agency', 'nexdirectory'),
                'private-owner' => __('Private Owner', 'nexdirectory'),
                'nonprofit' => __('Nonprofit / Community', 'nexdirectory'),
                'multi-location' => __('Multi-location Brand', 'nexdirectory'),
                'franchise' => __('Franchise', 'nexdirectory'),
            ];
            $directory_type_term_name = (string) ($directory_type_term_map[$directory_type] ?? '');
            if ($directory_type_term_name !== '') {
                $existing_type_ids = wp_get_post_terms($post_id, self::TAX_TYPE, ['fields' => 'ids']);
                if (is_wp_error($existing_type_ids) || !is_array($existing_type_ids)) {
                    $existing_type_ids = [];
                }
                $directory_type_term_id = $this->get_or_create_term_id(self::TAX_TYPE, $directory_type_term_name);
                if ($directory_type_term_id > 0) {
                    $existing_type_ids[] = (int) $directory_type_term_id;
                    wp_set_post_terms($post_id, array_values(array_unique(array_map('absint', $existing_type_ids))), self::TAX_TYPE, false);
                }
            }
        } else {
            $mode_option = sanitize_key((string) wp_unslash($_POST['eep_event_mode_option'] ?? 'in-person'));
            $price_option = sanitize_key((string) wp_unslash($_POST['eep_price_simple'] ?? 'free'));

            if ($is_events_profile) {
                $mode_name = $mode_option === 'video-stream' ? 'Online Stream Event' : 'In-person';
                $price_name = $price_option === 'paid' ? 'Paid' : 'Free';
            } elseif ($is_directory_profile) {
                $mode_name = $mode_option === 'video-stream' ? __('Online Service', 'nexdirectory') : __('On-site Service', 'nexdirectory');
                $price_name = $price_option === 'paid' ? __('Paid Service', 'nexdirectory') : __('Free Service', 'nexdirectory');
            } else {
                $mode_name = $mode_option === 'video-stream' ? __('Online', 'nexdirectory') : __('Standard', 'nexdirectory');
                $price_name = $price_option === 'paid' ? __('Paid', 'nexdirectory') : __('Free', 'nexdirectory');
            }

            $mode_term_id = $this->get_or_create_term_id(self::TAX_MODE, $mode_name);
            if ($mode_term_id > 0) {
                wp_set_post_terms($post_id, [$mode_term_id], self::TAX_MODE, false);
            }

            $price_term_id = $this->get_or_create_term_id(self::TAX_PRICE, $price_name);
            if ($price_term_id > 0) {
                wp_set_post_terms($post_id, [$price_term_id], self::TAX_PRICE, false);
            }
        }
        wp_set_post_terms($post_id, (array) $parsed_tags, self::TAX_TAG, false);
        $this->save_custom_taxonomy_terms_from_request($post_id);
        $missing_optional_sections = $this->get_event_optional_sections_missing($post_id, $profile_key);
        if (!empty($missing_optional_sections)) {
            update_post_meta($post_id, 'eep_missing_optional_sections', wp_json_encode($missing_optional_sections));
        } else {
            delete_post_meta($post_id, 'eep_missing_optional_sections');
        }
        $completion_hint_text = $this->get_event_optional_sections_hint_text($missing_optional_sections);

        if ($target_status === 'pending') {
            $this->notify_author_status($post_id, 'pending', '');
        } elseif ($target_status === 'publish') {
            $this->notify_author_status($post_id, 'publish', '');
        }
        if ($target_status === 'pending') {
            $message = sprintf(__('%s updated and sent for re-verification.', 'nexdirectory'), $singular_label);
        } elseif ($target_status === 'draft') {
            $message = sprintf(__('%s changes saved as draft.', 'nexdirectory'), $singular_label);
        } else {
            $message = sprintf(__('%s updated successfully.', 'nexdirectory'), $singular_label);
        }
        if ($duplicate_candidate_id > 0) {
            $message .= ' ' . __('Potential duplicate detected (same title/date/location).', 'nexdirectory');
        }
        if ($completion_hint_text !== '') {
            $message .= ' ' . $completion_hint_text;
        }

        $this->redirect_with_message(
            wp_get_referer() ?: home_url('/'),
            $message
        );
    }


    public function handle_delete_event() {
        $is_ajax_request = (string) wp_unslash($_POST['eep_ajax'] ?? '') === '1';
        $fallback_redirect = wp_get_referer() ?: home_url('/');
        $redirect_to = $this->get_safe_redirect_url(wp_unslash($_POST['redirect_to'] ?? ''), $fallback_redirect);
        $respond_error = function ($message, $status_code = 400) use ($is_ajax_request, $redirect_to) {
            if ($is_ajax_request) {
                wp_send_json_error(['message' => (string) $message], (int) $status_code);
            }
            $this->redirect_with_message($redirect_to, (string) $message, 'error');
        };
        $respond_success = function ($message, array $payload = []) use ($is_ajax_request, $redirect_to) {
            if ($is_ajax_request) {
                wp_send_json_success(array_merge(['message' => (string) $message], $payload));
            }
            $this->redirect_with_message($redirect_to, (string) $message);
        };

        if (!is_user_logged_in()) {
            $respond_error(__('You must be logged in.', 'nexdirectory'), 403);
        }
        if (!$this->user_can_submit_events(get_current_user_id())) {
            $respond_error(__('You do not have permission to delete events.', 'nexdirectory'), 403);
        }

        $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0;
        $is_owner = $post_id > 0 && (int) get_post_field('post_author', $post_id) === (int) get_current_user_id();
        if (!$post_id || get_post_type($post_id) !== self::POST_TYPE || (!$is_owner && !$this->user_can_moderate_events())) {
            $respond_error(__('Not allowed.', 'nexdirectory'), 403);
        }

        if (!isset($_POST['eep_delete_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_delete_nonce'])), 'eep_delete_event_' . $post_id)) {
            $respond_error(__('Invalid request.', 'nexdirectory'), 400);
        }

        wp_trash_post($post_id);

        $respond_success(
            __('Event moved to trash.', 'nexdirectory'),
            [
                'post_id' => (int) $post_id,
            ]
        );
    }


    private function resolve_live_toggle_target_status($user_id = 0, $existing_status = 'draft') {
        $user_id = absint((int) $user_id);
        $existing_status = sanitize_key((string) $existing_status);
        if ($this->user_can_moderate_events($user_id)) {
            return 'publish';
        }

        $edit_policy = sanitize_key((string) get_option('eep_user_edit_policy', 'require_approval'));
        if (!in_array($edit_policy, ['allow', 'require_approval', 'disallow'], true)) {
            $edit_policy = 'require_approval';
        }

        $auto_approve_after_first = (string) get_option('eep_auto_approve_edits_after_first_approval', '0') === '1';
        if (
            $edit_policy === 'require_approval'
            && $auto_approve_after_first
            && $existing_status === 'publish'
            && $this->user_has_published_listings($user_id)
        ) {
            $edit_policy = 'allow';
        }
        if ($this->user_can_bypass_edit_approval($user_id)) {
            $edit_policy = 'allow';
        }

        return $edit_policy === 'allow' ? 'publish' : 'pending';
    }


    public function handle_toggle_event_live_draft() {
        $post_id = isset($_POST['post_id']) ? absint((int) $_POST['post_id']) : 0;
        $fallback_redirect = wp_get_referer() ?: home_url('/');
        $redirect_to = $this->get_safe_redirect_url(wp_unslash($_POST['redirect_to'] ?? ''), $fallback_redirect);
        $is_ajax_request = (string) wp_unslash($_POST['eep_ajax'] ?? '') === '1';
        $respond_error = function ($message, $status_code = 400) use ($is_ajax_request, $redirect_to) {
            if ($is_ajax_request) {
                wp_send_json_error(['message' => (string) $message], (int) $status_code);
            }
            $this->redirect_with_message($redirect_to, (string) $message, 'error');
        };
        $respond_success = function ($message, array $payload = []) use ($is_ajax_request, $redirect_to) {
            if ($is_ajax_request) {
                wp_send_json_success(array_merge(['message' => (string) $message], $payload));
            }
            $this->redirect_with_message($redirect_to, (string) $message);
        };

        if (!is_user_logged_in()) {
            $respond_error(__('You must be logged in.', 'nexdirectory'), 403);
        }

        $profile_key = $this->resolve_profile_key_for_post($post_id);
        $ui_labels = $this->get_site_profile_ui_labels($profile_key);
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));

        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            $respond_error(__('Invalid listing selected.', 'nexdirectory'), 400);
        }

        if (
            !isset($_POST['eep_toggle_event_live_draft_nonce'])
            || !wp_verify_nonce(
                eep_sanitize_text(wp_unslash($_POST['eep_toggle_event_live_draft_nonce'])),
                'eep_toggle_event_live_draft_' . $post_id
            )
        ) {
            $respond_error(__('Invalid request.', 'nexdirectory'), 400);
        }

        $current_user_id = (int) get_current_user_id();
        $is_owner_or_team_owner = $this->can_manage_event_team($post_id, $current_user_id);
        if (!$is_owner_or_team_owner && !$this->user_can_moderate_events($current_user_id)) {
            $respond_error(__('You do not have permission to change listing status.', 'nexdirectory'), 403);
        }

        $desired_state_raw = wp_unslash($_POST['desired_state'] ?? '');
        if (is_array($desired_state_raw)) {
            $desired_state_raw = end($desired_state_raw);
        }
        $desired_state = sanitize_key((string) $desired_state_raw);
        if (!in_array($desired_state, ['live', 'draft'], true)) {
            $respond_error(__('Invalid status toggle request.', 'nexdirectory'), 400);
        }

        $existing_status = sanitize_key((string) get_post_status($post_id));
        if (!in_array($existing_status, ['publish', 'pending', 'draft'], true)) {
            $existing_status = 'draft';
        }

        if ($desired_state === 'draft') {
            if ($existing_status !== 'draft') {
                wp_update_post([
                    'ID' => $post_id,
                    'post_status' => 'draft',
                ]);
                update_post_meta($post_id, 'eep_moderation_action', 'owner_unpublished');
                $this->notify_author_status($post_id, 'draft', '');
            }
            $respond_success(
                sprintf(__('%s moved to draft.', 'nexdirectory'), $singular_label),
                [
                    'post_id' => (int) $post_id,
                    'status' => 'draft',
                    'status_label' => $this->event_status_badge('draft'),
                    'state_live' => false,
                    'pending_moderation' => false,
                ]
            );
        }

        $target_status = $this->resolve_live_toggle_target_status($current_user_id, $existing_status);
        if ($target_status === 'publish') {
            if ($existing_status !== 'publish') {
                wp_update_post([
                    'ID' => $post_id,
                    'post_status' => 'publish',
                ]);
                update_post_meta($post_id, 'eep_moderation_action', 'approve');
                $this->notify_author_status($post_id, 'publish', '');
            }
            $respond_success(
                sprintf(__('%s is now live.', 'nexdirectory'), $singular_label),
                [
                    'post_id' => (int) $post_id,
                    'status' => 'publish',
                    'status_label' => $this->event_status_badge('publish'),
                    'state_live' => true,
                    'pending_moderation' => false,
                ]
            );
        }

        wp_update_post([
            'ID' => $post_id,
            'post_status' => 'pending',
        ]);
        update_post_meta($post_id, 'eep_moderation_action', 'owner_live_request');
        update_post_meta($post_id, 'eep_moderation_note', '');
        $this->notify_admin_submission($post_id);
        $this->notify_author_status($post_id, 'pending', '');
        $respond_success(
            sprintf(
                __('%1$s submitted for moderation. It will go live once approved.', 'nexdirectory'),
                $singular_label
            ),
            [
                'post_id' => (int) $post_id,
                'status' => 'pending',
                'status_label' => $this->event_status_badge('pending'),
                'state_live' => false,
                'pending_moderation' => true,
            ]
        );
    }


    public function handle_save_filters() {
        if (!is_user_logged_in()) {
            wp_die(__('You must be logged in.', 'nexdirectory'));
        }
        if (!$this->user_can_access_platform(get_current_user_id())) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }
        if (!isset($_POST['eep_save_filters_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_save_filters_nonce'])), 'eep_save_filters_nonce')) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }
        $filters = [
            's' => eep_sanitize_text(wp_unslash($_POST['s'] ?? '')),
            'event_type' => eep_sanitize_text(wp_unslash($_POST['event_type'] ?? '')),
            'event_mode' => eep_sanitize_text(wp_unslash($_POST['event_mode'] ?? '')),
            'event_price' => eep_sanitize_text(wp_unslash($_POST['event_price'] ?? '')),
            'event_tag' => eep_sanitize_text(wp_unslash($_POST['event_tag'] ?? '')),
            'city' => eep_sanitize_text(wp_unslash($_POST['city'] ?? '')),
            'date_from' => eep_sanitize_text(wp_unslash($_POST['date_from'] ?? '')),
            'official' => eep_sanitize_text(wp_unslash($_POST['official'] ?? '')),
        ];
        if (!in_array((string) ($filters['official'] ?? ''), ['0', '1'], true)) {
            $filters['official'] = '';
        }
        update_user_meta(get_current_user_id(), 'eep_saved_filters', $filters);
        $redirect = !empty($_POST['redirect_to']) ? esc_url_raw(wp_unslash($_POST['redirect_to'])) : home_url('/');
        $home_host = wp_parse_url(home_url('/'), PHP_URL_HOST);
        $redirect_host = wp_parse_url($redirect, PHP_URL_HOST);
        if (!$redirect || ($redirect_host && $home_host && !hash_equals((string) $home_host, (string) $redirect_host))) {
            $redirect = home_url('/');
        }
        $this->redirect_with_message($redirect, __('Filters saved to your account.', 'nexdirectory'));
    }

    public function render_account($atts = []) {
        $current_user_id = is_user_logged_in() ? (int) get_current_user_id() : 0;
        $initial_tab = $this->user_is_account_app_non_owner_in_single_org_mode($current_user_id) ? 'saved' : 'account';
        return $this->render_account_app_template([
            'initial_tab' => $initial_tab,
            'login_message' => __('Please log in to manage your account.', 'nexdirectory'),
        ]);
    }


    public function handle_account_update() {
        if (!is_user_logged_in()) {
            wp_die(__('You must be logged in.', 'nexdirectory'));
        }
        if (!$this->user_can_access_platform(get_current_user_id())) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }

        if (!isset($_POST['eep_account_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_account_nonce'])), 'eep_account_update_nonce')) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }

        $userdata = [
            'ID' => get_current_user_id(),
            'display_name' => eep_sanitize_text(wp_unslash($_POST['display_name'] ?? '')),
            'user_email' => sanitize_email(wp_unslash($_POST['user_email'] ?? '')),
        ];
        $gdpr_marketing_opt_in = (string) wp_unslash($_POST['eep_gdpr_marketing_opt_in'] ?? '0') === '1';
        $event_messages_enabled = (string) wp_unslash($_POST['eep_event_messages_enabled'] ?? '1') === '1';

        $new_pass = isset($_POST['new_pass']) ? wp_unslash($_POST['new_pass']) : '';
        if (!empty($new_pass)) {
            $userdata['user_pass'] = $new_pass;
        }

        $result = wp_update_user($userdata);

        if (is_wp_error($result)) {
            $this->redirect_with_message(
                wp_get_referer() ?: home_url('/'),
                $result->get_error_message(),
                'error'
            );
        }
        $this->update_user_organizer_communication_consent(get_current_user_id(), $gdpr_marketing_opt_in);
        $this->update_user_event_message_preference(get_current_user_id(), $event_messages_enabled);

        $this->redirect_with_message(
            wp_get_referer() ?: home_url('/'),
            __('Account updated successfully.', 'nexdirectory')
        );
    }


    public function render_user_management($atts = []) {
        $atts = shortcode_atts([
            'limit' => 100,
            'admin_context' => 0,
        ], (array) $atts, 'eep_user_management');
        $limit = max(1, (int) $atts['limit']);
        $admin_context = (string) $atts['admin_context'] === '1';

        if (!$this->user_can_manage_organizers()) {
            return $this->redirect_to_login_screen(__('Unauthorized access. Admin login required.', 'nexdirectory'));
        }

        $search = isset($_GET['user_search']) ? eep_sanitize_text(wp_unslash($_GET['user_search'])) : '';
        $users = get_users([
            'search' => $search ? '*' . $search . '*' : '',
            'search_columns' => ['user_login', 'user_email', 'display_name'],
            'number' => $limit,
            'orderby' => 'registered',
            'order' => 'DESC',
        ]);
        $ui_labels = $this->get_site_profile_ui_labels();

        return $this->render_frontend_template('user-management.php', [
            'search' => $search,
            'users' => $users,
            'limit' => $limit,
            'rows_html' => $this->render_user_management_rows($users, $ui_labels),
            'ui_labels' => $ui_labels,
            'admin_context' => $admin_context,
        ]);
    }


    private function get_user_management_events($user_id, $limit = 12) {
        $user_id = absint($user_id);
        $limit = max(1, min(50, (int) $limit));
        if ($user_id <= 0) {
            return [];
        }
        return get_posts([
            'post_type' => self::POST_TYPE,
            'post_status' => ['publish', 'pending', 'draft'],
            'author' => $user_id,
            'posts_per_page' => $limit,
            'orderby' => 'date',
            'order' => 'DESC',
        ]);
    }


    private function render_user_management_rows($users, $ui_labels = []) {
        $users = is_array($users) ? $users : [];
        $ui_labels = is_array($ui_labels) ? $ui_labels : [];
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $plural_label = sanitize_text_field((string) ($ui_labels['plural'] ?? __('Events', 'nexdirectory')));
        $owner_singular = sanitize_text_field((string) ($ui_labels['owner_singular'] ?? __('Organizer', 'nexdirectory')));
        $singular_lower = sanitize_text_field((string) ($ui_labels['singular_lower'] ?? strtolower((string) $singular_label)));
        ob_start();
        if (empty($users)) {
            ?>
            <tr>
                <td colspan="5" class="eep-admin-ops-empty-state" data-label="<?php esc_attr_e('Users', 'nexdirectory'); ?>">
                    <?php esc_html_e('No users found for this query.', 'nexdirectory'); ?>
                </td>
            </tr>
            <?php
            return (string) ob_get_clean();
        }

        foreach ($users as $user) :
            if (!$user instanceof WP_User) {
                continue;
            }
            $events = $this->get_user_management_events($user->ID, 14);
            $role = !empty($user->roles) ? (string) $user->roles[0] : 'subscriber';
            $verified = get_user_meta($user->ID, 'eep_verified_organizer', true) === '1';
            $trusted_editor = $this->user_is_trusted_editor($user->ID);
            $is_admin = in_array('administrator', (array) $user->roles, true);
            $events_page_url = $this->get_platform_page_url(
                'eep_page_events_url',
                $this->get_profile_route_slugs('events'),
                $this->get_profile_route_default_url('events')
            );
            if ($events_page_url === '') {
                $events_page_url = home_url('/');
            }
            $user_events_url = eep_add_query_arg('organizer', $user->ID, $events_page_url);
            $status_counts = ['publish' => 0, 'pending' => 0, 'draft' => 0];
            foreach ($events as $event_post) {
                $status = (string) get_post_status($event_post);
                if (isset($status_counts[$status])) {
                    $status_counts[$status]++;
                }
            }
            $display_name = (string) ($user->display_name ?: $user->user_login);
            $joined_label = wp_date(get_option('date_format'), strtotime((string) $user->user_registered));
            $role_label = ucwords(str_replace('_', ' ', $role));
            ?>
            <tr>
                <td class="eep-user-cell eep-admin-ops-user" data-label="<?php esc_attr_e('User', 'nexdirectory'); ?>">
                    <strong><?php echo esc_html($display_name); ?></strong><br>
                    <a href="mailto:<?php echo esc_attr(sanitize_email($user->user_email)); ?>"><?php echo esc_html($user->user_email); ?></a><br>
                    <small class="eep-admin-ops-meta"><?php echo esc_html(sprintf(__('Joined %s', 'nexdirectory'), $joined_label)); ?></small>
                </td>
                <td data-label="<?php esc_attr_e('Role', 'nexdirectory'); ?>">
                    <div class="eep-admin-ops-stack">
                        <span class="eep-admin-ops-pill"><?php echo esc_html($role_label); ?></span>
                        <?php if (!$is_admin) : ?>
                            <small class="eep-admin-ops-meta"><?php echo esc_html($trusted_editor ? __('Trusted edits enabled', 'nexdirectory') : __('Trusted edits require approval', 'nexdirectory')); ?></small>
                        <?php endif; ?>
                    </div>
                </td>
                <td data-label="<?php esc_attr_e('Verified', 'nexdirectory'); ?>">
                    <span class="eep-admin-ops-pill <?php echo $verified ? 'eep-admin-ops-pill-success' : 'eep-admin-ops-pill-muted'; ?>">
                        <?php echo esc_html($verified ? __('Yes', 'nexdirectory') : __('No', 'nexdirectory')); ?>
                    </span>
                </td>
                <td data-label="<?php echo esc_attr($plural_label); ?>">
                    <div class="eep-admin-ops-stack">
                        <strong><?php echo esc_html((string) count($events)); ?></strong>
                        <small class="eep-admin-ops-meta"><?php echo esc_html(sprintf(__('Published %d · Pending %d · Draft %d', 'nexdirectory'), (int) $status_counts['publish'], (int) $status_counts['pending'], (int) $status_counts['draft'])); ?></small>
                    </div>
                </td>
                <td class="eep-actions eep-admin-actions eep-admin-ops-actions" data-label="<?php esc_attr_e('Actions', 'nexdirectory'); ?>">
                    <div class="eep-admin-ops-action-grid">
                        <a class="eep-admin-ops-button eep-admin-ops-button-secondary" href="<?php echo esc_url(admin_url('user-edit.php?user_id=' . $user->ID)); ?>">
                            <?php esc_html_e('Open Profile', 'nexdirectory'); ?>
                        </a>
                        <a class="eep-admin-ops-button eep-admin-ops-button-secondary" href="<?php echo esc_url($user_events_url); ?>">
                            <?php echo esc_html(sprintf(__('View %s', 'nexdirectory'), $plural_label)); ?>
                        </a>
                        <?php if (!$is_admin) : ?>
                            <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" class="eep-inline-form">
                                <input type="hidden" name="action" value="eep_manage_user_role">
                                <input type="hidden" name="user_id" value="<?php echo esc_attr((string) $user->ID); ?>">
                                <input type="hidden" name="role" value="<?php echo esc_attr($role === 'event_submitter' ? 'subscriber' : 'event_submitter'); ?>">
                                <?php wp_nonce_field('eep_manage_user_role_' . $user->ID, 'eep_manage_user_nonce'); ?>
                                <button class="eep-admin-ops-button" type="submit">
                                    <?php echo esc_html($role === 'event_submitter' ? __('Set Subscriber', 'nexdirectory') : sprintf(__('Set %s Submitter', 'nexdirectory'), $singular_label)); ?>
                                </button>
                            </form>
                            <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" class="eep-inline-form">
                                <input type="hidden" name="action" value="eep_toggle_verified">
                                <input type="hidden" name="user_id" value="<?php echo esc_attr((string) $user->ID); ?>">
                                <?php wp_nonce_field('eep_toggle_verified_' . $user->ID, 'eep_toggle_verified_nonce'); ?>
                                <button class="eep-admin-ops-button eep-admin-ops-button-secondary" type="submit">
                                    <?php echo esc_html($verified ? __('Remove Badge', 'nexdirectory') : sprintf(__('Verify %s', 'nexdirectory'), $owner_singular)); ?>
                                </button>
                            </form>
                            <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" class="eep-inline-form">
                                <input type="hidden" name="action" value="eep_toggle_user_edit_trust">
                                <input type="hidden" name="user_id" value="<?php echo esc_attr((string) $user->ID); ?>">
                                <?php wp_nonce_field('eep_toggle_user_edit_trust_' . $user->ID, 'eep_toggle_user_edit_trust_nonce'); ?>
                                <button class="eep-admin-ops-button eep-admin-ops-button-secondary" type="submit">
                                    <?php echo esc_html($trusted_editor ? __('Require Approval', 'nexdirectory') : __('Trust Edits', 'nexdirectory')); ?>
                                </button>
                            </form>
                        <?php endif; ?>
                    </div>

                    <?php if ($is_admin) : ?>
                        <p class="eep-admin-ops-meta"><?php esc_html_e('Role and trust controls are unavailable for administrator accounts.', 'nexdirectory'); ?></p>
                    <?php endif; ?>

                    <?php if (!empty($events)) : ?>
                        <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" class="eep-user-event-tools eep-admin-ops-user-tools">
                            <input type="hidden" name="action" value="eep_manage_user_event">
                            <input type="hidden" name="user_id" value="<?php echo esc_attr((string) $user->ID); ?>">
                            <?php wp_nonce_field('eep_manage_user_event_' . $user->ID, 'eep_manage_user_event_nonce'); ?>
                            <label><?php echo esc_html($singular_label); ?></label>
                            <select name="event_id">
                                <?php foreach ($events as $event_post) : ?>
                                    <option value="<?php echo esc_attr((string) $event_post->ID); ?>">
                                        <?php echo esc_html(get_the_title($event_post->ID) . ' (' . ucfirst((string) get_post_status($event_post->ID)) . ')'); ?>
                                    </option>
                                <?php endforeach; ?>
                            </select>
                            <input type="text" name="admin_subject" placeholder="<?php esc_attr_e('Email subject (optional)', 'nexdirectory'); ?>">
                            <textarea name="admin_message" rows="2" placeholder="<?php echo esc_attr(sprintf(__('Email note to %1$s owner (optional)', 'nexdirectory'), $singular_lower)); ?>"></textarea>
                            <div class="eep-actions-inline">
                                <button class="eep-admin-ops-button eep-admin-ops-button-secondary" type="submit" name="intent" value="open_event"><?php echo esc_html(sprintf(__('Open %s', 'nexdirectory'), $singular_label)); ?></button>
                                <button class="eep-admin-ops-button" type="submit" name="intent" value="publish_event"><?php echo esc_html(sprintf(__('Publish %s', 'nexdirectory'), $singular_label)); ?></button>
                                <button class="eep-admin-ops-button eep-admin-ops-button-secondary" type="submit" name="intent" value="unpublish_event"><?php echo esc_html(sprintf(__('Unpublish %s', 'nexdirectory'), $singular_label)); ?></button>
                                <button class="eep-admin-ops-button eep-admin-ops-button-secondary" type="submit" name="intent" value="email_owner"><?php echo esc_html(sprintf(__('Email %s', 'nexdirectory'), $owner_singular)); ?></button>
                            </div>
                        </form>
                    <?php endif; ?>
                </td>
            </tr>
            <?php
        endforeach;

        return (string) ob_get_clean();
    }


    private function event_webapp_generate_id($prefix = 'ew') {
        $prefix = sanitize_key((string) $prefix);
        if ($prefix === '') {
            $prefix = 'ew';
        }
        $seed = function_exists('wp_generate_uuid4') ? (string) wp_generate_uuid4() : (string) uniqid('', true);
        return sanitize_key($prefix . substr(md5($seed), 0, 20));
    }


    private function sanitize_event_webapp_install_token($token) {
        $token = trim((string) $token);
        if ($token === '') {
            return '';
        }
        $token = strtolower($token);
        return (string) preg_replace('/[^a-z0-9]/', '', $token);
    }


    private function append_event_webapp_install_query_args($url, array $auth_identity = []) {
        $url = esc_url_raw((string) $url);
        return $url;
    }


    private function get_event_webapp_install_cookie_name($post_id) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return '';
        }
        return 'eep_webapp_install_' . (string) $post_id;
    }


    private function get_event_webapp_install_registry($post_id) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return [];
        }
        $raw = get_post_meta($post_id, 'eep_webapp_install_registry', true);
        if (!is_array($raw)) {
            $decoded = json_decode((string) $raw, true);
            $raw = is_array($decoded) ? $decoded : [];
        }

        $registry = [];
        foreach ((array) $raw as $install_key => $row) {
            if (!is_array($row)) {
                continue;
            }
            $install_id = sanitize_key(is_string($install_key) ? $install_key : (string) ($row['id'] ?? ''));
            if ($install_id === '') {
                continue;
            }
            $user_id = absint((int) ($row['user_id'] ?? 0));
            if ($user_id <= 0) {
                continue;
            }
            $token_hash = sanitize_text_field((string) ($row['token_hash'] ?? ''));
            if ($token_hash === '') {
                continue;
            }
            $registry[$install_id] = [
                'id' => $install_id,
                'user_id' => $user_id,
                'token_hash' => $token_hash,
                'created_at_gmt' => sanitize_text_field((string) ($row['created_at_gmt'] ?? '')),
                'last_seen_gmt' => sanitize_text_field((string) ($row['last_seen_gmt'] ?? '')),
                'label' => sanitize_text_field((string) ($row['label'] ?? '')),
            ];
        }

        return $registry;
    }


    private function update_event_webapp_install_registry($post_id, array $registry) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return;
        }

        $normalized = [];
        foreach ($registry as $install_id => $row) {
            if (!is_array($row)) {
                continue;
            }
            $install_id = sanitize_key(is_string($install_id) ? $install_id : (string) ($row['id'] ?? ''));
            $user_id = absint((int) ($row['user_id'] ?? 0));
            $token_hash = sanitize_text_field((string) ($row['token_hash'] ?? ''));
            if ($install_id === '' || $user_id <= 0 || $token_hash === '') {
                continue;
            }
            $normalized[$install_id] = [
                'id' => $install_id,
                'user_id' => $user_id,
                'token_hash' => $token_hash,
                'created_at_gmt' => sanitize_text_field((string) ($row['created_at_gmt'] ?? '')),
                'last_seen_gmt' => sanitize_text_field((string) ($row['last_seen_gmt'] ?? '')),
                'label' => sanitize_text_field((string) ($row['label'] ?? '')),
            ];
        }

        if (empty($normalized)) {
            delete_post_meta($post_id, 'eep_webapp_install_registry');
            return;
        }

        uasort($normalized, static function ($a, $b) {
            $a_ts = strtotime((string) ($a['last_seen_gmt'] ?? ''));
            $b_ts = strtotime((string) ($b['last_seen_gmt'] ?? ''));
            if ($a_ts !== $b_ts) {
                return $b_ts <=> $a_ts;
            }
            return strcasecmp((string) ($a['id'] ?? ''), (string) ($b['id'] ?? ''));
        });
        $normalized = array_slice($normalized, 0, 800, true);

        update_post_meta($post_id, 'eep_webapp_install_registry', $normalized);
    }


    private function set_event_webapp_install_cookie($post_id, $install_id, $install_token) {
        $post_id = absint((int) $post_id);
        $install_id = sanitize_key((string) $install_id);
        $install_token = $this->sanitize_event_webapp_install_token($install_token);
        if ($post_id <= 0 || $install_id === '' || $install_token === '') {
            return;
        }
        $cookie_name = $this->get_event_webapp_install_cookie_name($post_id);
        if ($cookie_name === '') {
            return;
        }
        $cookie_value = $install_id . '.' . $install_token;
        $expires = time() + (2 * YEAR_IN_SECONDS);
        $secure = is_ssl();

        if (!headers_sent()) {
            setcookie($cookie_name, $cookie_value, $expires, '/', '', $secure, true);
        }
        $_COOKIE[$cookie_name] = $cookie_value;
    }


    private function get_event_webapp_install_credentials($post_id, $request = null) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return ['install_id' => '', 'install_token' => ''];
        }

        $install_id = '';
        $install_token = '';

        if (class_exists('WP_REST_Request') && $request instanceof WP_REST_Request) {
            $install_id = sanitize_key((string) $request->get_header('x-eep-webapp-install-id'));
            $install_token = $this->sanitize_event_webapp_install_token((string) $request->get_header('x-eep-webapp-install-token'));
        }

        if ($install_id === '' || $install_token === '') {
            $cookie_name = $this->get_event_webapp_install_cookie_name($post_id);
            if ($cookie_name !== '' && isset($_COOKIE[$cookie_name])) {
                $cookie_raw = (string) wp_unslash($_COOKIE[$cookie_name]); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
                $parts = explode('.', $cookie_raw, 2);
                if (!empty($parts[0]) && !empty($parts[1])) {
                    $install_id = sanitize_key((string) $parts[0]);
                    $install_token = $this->sanitize_event_webapp_install_token((string) $parts[1]);
                }
            }
        }

        return [
            'install_id' => $install_id,
            'install_token' => $install_token,
        ];
    }


    private function get_event_webapp_install_identity($post_id, $request = null) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return null;
        }

        $credentials = $this->get_event_webapp_install_credentials($post_id, $request);
        $install_id = sanitize_key((string) ($credentials['install_id'] ?? ''));
        $install_token = $this->sanitize_event_webapp_install_token((string) ($credentials['install_token'] ?? ''));
        if ($install_id === '' || $install_token === '') {
            return null;
        }

        $registry = $this->get_event_webapp_install_registry($post_id);
        if (empty($registry[$install_id]) || !is_array($registry[$install_id])) {
            return null;
        }
        $row = (array) $registry[$install_id];
        $token_hash = sanitize_text_field((string) ($row['token_hash'] ?? ''));
        if ($token_hash === '' || !wp_check_password($install_token, $token_hash)) {
            return null;
        }

        $user_id = absint((int) ($row['user_id'] ?? 0));
        if ($user_id <= 0 || !get_user_by('id', $user_id)) {
            return null;
        }

        $row['last_seen_gmt'] = current_time('mysql', true);
        $registry[$install_id] = $row;
        $this->update_event_webapp_install_registry($post_id, $registry);
        $this->set_event_webapp_install_cookie($post_id, $install_id, $install_token);

        return [
            'user_id' => $user_id,
            'source' => 'install',
            'install_id' => $install_id,
            'install_token' => $install_token,
        ];
    }


    private function issue_event_webapp_install_identity($post_id, $user_id, $request = null) {
        $post_id = absint((int) $post_id);
        $user_id = absint((int) $user_id);
        if ($post_id <= 0 || $user_id <= 0) {
            return null;
        }

        $existing = $this->get_event_webapp_install_identity($post_id, $request);
        if (is_array($existing) && absint((int) ($existing['user_id'] ?? 0)) === $user_id) {
            return $existing;
        }

        $registry = $this->get_event_webapp_install_registry($post_id);
        $install_id = $this->event_webapp_generate_id('ins');
        while ($install_id === '' || isset($registry[$install_id])) {
            $install_id = $this->event_webapp_generate_id('ins');
        }

        try {
            $install_token = bin2hex(random_bytes(32));
        } catch (Exception $e) {
            $install_token = strtolower(wp_generate_password(64, false, false));
            $install_token = $this->sanitize_event_webapp_install_token($install_token);
        }
        if ($install_token === '') {
            $install_token = $this->sanitize_event_webapp_install_token((string) wp_generate_password(48, false, false));
        }

        $now_gmt = current_time('mysql', true);
        $label = sanitize_text_field(substr((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 180));
        $registry[$install_id] = [
            'id' => $install_id,
            'user_id' => $user_id,
            'token_hash' => wp_hash_password($install_token),
            'created_at_gmt' => $now_gmt,
            'last_seen_gmt' => $now_gmt,
            'label' => $label,
        ];

        $user_install_ids = [];
        foreach ($registry as $row_install_id => $row) {
            if (!is_array($row)) {
                continue;
            }
            if (absint((int) ($row['user_id'] ?? 0)) === $user_id) {
                $user_install_ids[] = sanitize_key((string) $row_install_id);
            }
        }
        if (count($user_install_ids) > 6) {
            usort($user_install_ids, static function ($a, $b) use ($registry) {
                $a_ts = strtotime((string) ($registry[$a]['last_seen_gmt'] ?? ''));
                $b_ts = strtotime((string) ($registry[$b]['last_seen_gmt'] ?? ''));
                if ($a_ts !== $b_ts) {
                    return $a_ts <=> $b_ts;
                }
                return strcasecmp((string) $a, (string) $b);
            });
            while (count($user_install_ids) > 6) {
                $drop_id = array_shift($user_install_ids);
                if ($drop_id && $drop_id !== $install_id) {
                    unset($registry[$drop_id]);
                }
            }
        }

        $this->update_event_webapp_install_registry($post_id, $registry);
        $this->set_event_webapp_install_cookie($post_id, $install_id, $install_token);

        return [
            'user_id' => $user_id,
            'source' => 'install',
            'install_id' => $install_id,
            'install_token' => $install_token,
        ];
    }


    private function resolve_event_webapp_auth_identity($post_id, $request = null, $issue_install_for_session = false) {
        $post_id = absint((int) $post_id);
        $identity = [
            'user_id' => 0,
            'source' => 'guest',
            'install_id' => '',
            'install_token' => '',
        ];
        if ($post_id <= 0) {
            return $identity;
        }

        $session_user_id = is_user_logged_in() ? absint((int) get_current_user_id()) : 0;
        if ($session_user_id > 0) {
            $identity['user_id'] = $session_user_id;
            $identity['source'] = 'session';
            if ($issue_install_for_session) {
                $install_identity = $this->issue_event_webapp_install_identity($post_id, $session_user_id, $request);
                if (is_array($install_identity)) {
                    $identity['install_id'] = sanitize_key((string) ($install_identity['install_id'] ?? ''));
                    $identity['install_token'] = $this->sanitize_event_webapp_install_token((string) ($install_identity['install_token'] ?? ''));
                }
            }
            return $identity;
        }

        $install_identity = $this->get_event_webapp_install_identity($post_id, $request);
        if (is_array($install_identity)) {
            $identity['user_id'] = absint((int) ($install_identity['user_id'] ?? 0));
            $identity['source'] = 'install';
            $identity['install_id'] = sanitize_key((string) ($install_identity['install_id'] ?? ''));
            $identity['install_token'] = $this->sanitize_event_webapp_install_token((string) ($install_identity['install_token'] ?? ''));
        }

        return $identity;
    }


    private function parse_event_webapp_user_ids($raw_ids) {
        if (is_array($raw_ids)) {
            $raw_parts = $raw_ids;
        } else {
            $raw_parts = preg_split('/[\s,]+/', (string) $raw_ids) ?: [];
        }
        return array_values(array_unique(array_filter(array_map('absint', $raw_parts))));
    }

    private function event_webapp_user_can_access_event($post_id, $user_id = 0, array $settings = [], $can_manage = null) {
        $post_id = absint((int) $post_id);
        $user_id = absint((int) $user_id);
        if ($post_id <= 0) {
            return false;
        }
        if ($can_manage === null) {
            $can_manage = $this->event_webapp_user_can_manage_event($post_id, $user_id);
        }
        if (!empty($can_manage)) {
            return true;
        }
        if (empty($settings)) {
            $settings = $this->get_event_webapp_settings($post_id);
        }

        $requires_login = !empty($settings['requires_login']);
        if ($requires_login && $user_id <= 0) {
            return false;
        }

        $allowed_user_ids = !empty($settings['allowed_user_ids']) && is_array($settings['allowed_user_ids'])
            ? $this->parse_event_webapp_user_ids((array) $settings['allowed_user_ids'])
            : $this->parse_event_webapp_user_ids((string) get_post_meta($post_id, 'eep_webapp_allowed_user_ids', true));
        if (!empty($allowed_user_ids) && !in_array($user_id, $allowed_user_ids, true)) {
            return false;
        }

        return true;
    }


    private function event_webapp_user_can_manage_event($post_id, $user_id = 0) {
        $post_id = absint((int) $post_id);
        $user_id = absint((int) $user_id);
        if ($post_id <= 0 || $user_id <= 0) {
            return false;
        }
        if ($this->user_can_moderate_events($user_id)) {
            return true;
        }
        if ($this->user_can_edit_event($post_id, $user_id)) {
            return true;
        }
        if ($this->can_manage_event_team($post_id, $user_id)) {
            return true;
        }
        return false;
    }


    private function event_webapp_user_can_moderate_chat($post_id, $user_id, array $settings = [], $can_manage = null) {
        $post_id = absint((int) $post_id);
        $user_id = absint((int) $user_id);
        if ($post_id <= 0 || $user_id <= 0) {
            return false;
        }

        if ($can_manage === null) {
            $can_manage = $this->event_webapp_user_can_manage_event($post_id, $user_id);
        }
        if (!empty($can_manage)) {
            return true;
        }

        if ($this->user_has_event_team_role($post_id, $user_id, ['owner', 'editor', 'app_moderator', 'support'], ['active'], 'webapp')) {
            return true;
        }

        return false;
    }


    private function get_event_webapp_settings($post_id) {
        $post_id = absint((int) $post_id);
        $accent_default = sanitize_hex_color((string) get_option('eep_style_accent_color', '#4F46E5')) ?: '#4F46E5';
        $site_icon_url = '';
        $site_icon_id = absint((int) get_option('site_icon', 0));
        if ($site_icon_id > 0) {
            $resolved_site_icon = wp_get_attachment_image_url($site_icon_id, 'full');
            if (is_string($resolved_site_icon) && $resolved_site_icon !== '') {
                $site_icon_url = esc_url_raw($resolved_site_icon);
            }
        }

        $asset_urls = $this->parse_multiline_urls((string) get_post_meta($post_id, 'eep_webapp_asset_urls', true));
        $asset_urls = array_values(array_unique(array_filter(array_map('esc_url_raw', $asset_urls))));

        $allowed_user_ids = $this->parse_event_webapp_user_ids((string) get_post_meta($post_id, 'eep_webapp_allowed_user_ids', true));

        $push_provider = sanitize_key((string) get_post_meta($post_id, 'eep_webapp_push_provider', true));
        if (!in_array($push_provider, ['none', 'onesignal', 'firebase', 'custom'], true)) {
            $push_provider = 'none';
        }
        $chat_moderation = sanitize_key((string) get_post_meta($post_id, 'eep_webapp_chat_moderation', true));
        if (!in_array($chat_moderation, ['open', 'moderated', 'admins-only'], true)) {
            $chat_moderation = 'open';
        }
        $qa_moderation = sanitize_key((string) get_post_meta($post_id, 'eep_webapp_qa_moderation', true));
        if (!in_array($qa_moderation, ['open', 'moderated', 'admins-only'], true)) {
            $qa_moderation = 'open';
        }
        $polls_vote_mode = sanitize_key((string) get_post_meta($post_id, 'eep_webapp_polls_vote_mode', true));
        if (!in_array($polls_vote_mode, ['single', 'multiple'], true)) {
            $polls_vote_mode = 'single';
        }
        $accent_color = sanitize_hex_color((string) get_post_meta($post_id, 'eep_webapp_accent_color', true));
        if ($accent_color === null || $accent_color === '') {
            $accent_color = $accent_default;
        }

        $icon_url = esc_url_raw((string) get_post_meta($post_id, 'eep_webapp_icon_url', true));
        if ($icon_url === '') {
            $icon_url = $site_icon_url;
        }
        $splash_url = esc_url_raw((string) get_post_meta($post_id, 'eep_webapp_splash_url', true));
        $header_logo_url = esc_url_raw((string) get_post_meta($post_id, 'eep_webapp_header_logo_url', true));
        if ($header_logo_url === '') {
            $header_logo_url = $icon_url;
        }
        $loading_bg_color = sanitize_hex_color((string) get_post_meta($post_id, 'eep_webapp_loading_bg_color', true));
        if ($loading_bg_color === null || $loading_bg_color === '') {
            $loading_bg_color = '#ffffff';
        }
        $requires_login_raw = (string) get_post_meta($post_id, 'eep_webapp_requires_login', true);
        $requires_login = $requires_login_raw === '' ? true : $requires_login_raw === '1';

        return [
            'enabled' => (string) get_post_meta($post_id, 'eep_webapp_enabled', true) === '1',
            'title' => sanitize_text_field((string) get_post_meta($post_id, 'eep_webapp_title', true)),
            'subtitle' => eep_sanitize_textarea((string) get_post_meta($post_id, 'eep_webapp_subtitle', true)),
            'icon_url' => $icon_url,
            'splash_url' => $splash_url,
            'header_logo_url' => $header_logo_url,
            'loading_bg_color' => $loading_bg_color,
            'accent_color' => $accent_color,
            'push_enabled' => (string) get_post_meta($post_id, 'eep_webapp_push_enabled', true) === '1',
            'push_provider' => $push_provider,
            'push_key' => sanitize_text_field((string) get_post_meta($post_id, 'eep_webapp_push_key', true)),
            'requires_login' => $requires_login,
            'allowed_user_ids' => $allowed_user_ids,
            'chat_moderation' => $chat_moderation,
            'qa_moderation' => $qa_moderation,
            'qa_allow_attendee_answers' => (string) get_post_meta($post_id, 'eep_webapp_qa_allow_attendee_answers', true) === '1',
            'qa_allow_anonymous' => (string) get_post_meta($post_id, 'eep_webapp_qa_allow_anonymous', true) === '1',
            'polls_vote_mode' => $polls_vote_mode,
            'enable_chat' => (string) get_post_meta($post_id, 'eep_webapp_enable_chat', true) !== '0',
            'enable_qa' => (string) get_post_meta($post_id, 'eep_webapp_enable_qa', true) !== '0',
            'enable_polls' => (string) get_post_meta($post_id, 'eep_webapp_enable_polls', true) !== '0',
            'enable_map' => (string) get_post_meta($post_id, 'eep_webapp_enable_map', true) !== '0',
            'enable_notifications' => (string) get_post_meta($post_id, 'eep_webapp_enable_notifications', true) !== '0',
            'enable_attendees' => (string) get_post_meta($post_id, 'eep_webapp_enable_attendees', true) !== '0',
            'enable_custom_itinerary' => (string) get_post_meta($post_id, 'eep_webapp_enable_custom_itinerary', true) !== '0',
            'asset_urls' => $asset_urls,
        ];
    }


    private function get_event_webapp_store_rows($post_id, $meta_key) {
        $post_id = absint((int) $post_id);
        $meta_key = sanitize_key((string) $meta_key);
        if ($post_id <= 0 || $meta_key === '') {
            return [];
        }
        $raw = get_post_meta($post_id, $meta_key, true);
        if (is_array($raw)) {
            return array_values($raw);
        }
        $decoded = json_decode((string) $raw, true);
        if (!is_array($decoded)) {
            return [];
        }
        return array_values($decoded);
    }


    private function update_event_webapp_store_rows($post_id, $meta_key, array $rows, $max_rows = 300) {
        $post_id = absint((int) $post_id);
        $meta_key = sanitize_key((string) $meta_key);
        if ($post_id <= 0 || $meta_key === '') {
            return;
        }
        $max_rows = max(20, min(1000, (int) $max_rows));
        $rows = array_values(array_slice($rows, -$max_rows));
        update_post_meta($post_id, $meta_key, wp_json_encode($rows));
    }


    private function format_event_webapp_session_time_label($start_raw, $end_raw) {
        $start_raw = trim((string) $start_raw);
        $end_raw = trim((string) $end_raw);
        if ($start_raw === '' && $end_raw === '') {
            return '';
        }
        $start_label = $start_raw !== '' ? (string) $this->format_event_datetime_for_display($start_raw) : '';
        $end_label = $end_raw !== '' ? (string) $this->format_event_datetime_for_display($end_raw) : '';
        if ($start_label !== '' && $end_label !== '' && $start_label !== $end_label) {
            return $start_label . ' - ' . $end_label;
        }
        return $start_label !== '' ? $start_label : $end_label;
    }


    private function build_event_webapp_session_rows($post_id) {
        $post_id = absint((int) $post_id);
        $sessions = $this->parse_sessions((string) get_post_meta($post_id, 'eep_sessions', true));
        if (!is_array($sessions) || empty($sessions)) {
            return [];
        }

        $rows = [];
        foreach (array_values($sessions) as $index => $session_row) {
            if (!is_array($session_row)) {
                continue;
            }
            $title = sanitize_text_field((string) ($session_row['title'] ?? ''));
            $start_raw = sanitize_text_field((string) ($session_row['start'] ?? ''));
            $end_raw = sanitize_text_field((string) ($session_row['end'] ?? ''));
            $location = sanitize_text_field((string) ($session_row['location'] ?? ''));
            $speaker = sanitize_text_field((string) ($session_row['speaker'] ?? ''));
            $day_label = sanitize_text_field((string) ($session_row['day'] ?? ''));
            $session_private_notes = eep_sanitize_textarea((string) ($session_row['notes'] ?? ''));
            $attachments = [];
            foreach ((array) ($session_row['attachments'] ?? []) as $attachment_url) {
                $normalized_attachment_url = esc_url_raw((string) $attachment_url);
                if ($normalized_attachment_url !== '') {
                    $attachments[] = $normalized_attachment_url;
                }
            }
            $attachments = array_values(array_unique($attachments));

            $start_ts = $start_raw !== '' ? strtotime($start_raw) : false;
            $end_ts = $end_raw !== '' ? strtotime($end_raw) : false;
            if ($day_label === '' && $start_ts) {
                $day_label = wp_date(get_option('date_format'), (int) $start_ts);
            }
            if ($day_label === '') {
                $day_label = sprintf(__('Day %d', 'nexdirectory'), (int) ($index + 1));
            }
            $session_key = sanitize_key((string) ($session_row['session_key'] ?? ''));
            if ($session_key === '') {
                $session_key = sanitize_key('s' . substr(md5((string) $post_id . '|' . (string) $index . '|' . $title . '|' . $start_raw . '|' . $end_raw), 0, 24));
            }

            if ($title === '' && $start_raw === '' && $end_raw === '' && $location === '' && $speaker === '' && empty($attachments) && $session_private_notes === '') {
                continue;
            }

            $rows[] = [
                'id' => $session_key,
                'title' => $title !== '' ? $title : sprintf(__('Session %d', 'nexdirectory'), (int) ($index + 1)),
                'start' => $start_raw,
                'end' => $end_raw,
                'start_ts' => $start_ts ? (int) $start_ts : 0,
                'end_ts' => $end_ts ? (int) $end_ts : 0,
                'time' => $this->format_event_webapp_session_time_label($start_raw, $end_raw),
                'location' => $location,
                'speaker' => $speaker,
                'day' => $day_label,
                // Personal itinerary notes are user-scoped and stored separately in user meta.
                'notes' => '',
                'attachments' => $attachments,
            ];
        }

        usort($rows, static function ($a, $b) {
            $a_ts = absint((int) ($a['start_ts'] ?? 0));
            $b_ts = absint((int) ($b['start_ts'] ?? 0));
            if ($a_ts > 0 && $b_ts > 0 && $a_ts !== $b_ts) {
                return $a_ts <=> $b_ts;
            }
            return strcasecmp((string) ($a['title'] ?? ''), (string) ($b['title'] ?? ''));
        });

        return $rows;
    }


    private function build_event_webapp_day_groups(array $session_rows) {
        if (empty($session_rows)) {
            return [];
        }
        $grouped = [];
        $order = [];
        foreach ($session_rows as $session_row) {
            if (!is_array($session_row)) {
                continue;
            }
            $day_label = sanitize_text_field((string) ($session_row['day'] ?? ''));
            if ($day_label === '') {
                $day_label = __('Day', 'nexdirectory');
            }
            $day_key = sanitize_key($day_label);
            if ($day_key === '') {
                $day_key = 'day-' . (string) (count($order) + 1);
            }
            if (!isset($grouped[$day_key])) {
                $grouped[$day_key] = [
                    'id' => $day_key,
                    'date' => $day_label,
                    'title' => $day_label,
                    'sort_ts' => absint((int) ($session_row['start_ts'] ?? 0)),
                    'items' => [],
                ];
                $order[] = $day_key;
            } elseif ($grouped[$day_key]['sort_ts'] <= 0 && absint((int) ($session_row['start_ts'] ?? 0)) > 0) {
                $grouped[$day_key]['sort_ts'] = absint((int) ($session_row['start_ts'] ?? 0));
            }
            $grouped[$day_key]['items'][] = $session_row;
        }

        usort($order, static function ($a, $b) use ($grouped) {
            $a_sort = absint((int) ($grouped[$a]['sort_ts'] ?? 0));
            $b_sort = absint((int) ($grouped[$b]['sort_ts'] ?? 0));
            if ($a_sort > 0 && $b_sort > 0 && $a_sort !== $b_sort) {
                return $a_sort <=> $b_sort;
            }
            return strcasecmp((string) ($grouped[$a]['title'] ?? ''), (string) ($grouped[$b]['title'] ?? ''));
        });

        $days = [];
        foreach ($order as $day_key) {
            $day_row = $grouped[$day_key];
            if (!is_array($day_row)) {
                continue;
            }
            $items = isset($day_row['items']) && is_array($day_row['items']) ? $day_row['items'] : [];
            usort($items, static function ($a, $b) {
                $a_ts = absint((int) ($a['start_ts'] ?? 0));
                $b_ts = absint((int) ($b['start_ts'] ?? 0));
                if ($a_ts > 0 && $b_ts > 0 && $a_ts !== $b_ts) {
                    return $a_ts <=> $b_ts;
                }
                return strcasecmp((string) ($a['title'] ?? ''), (string) ($b['title'] ?? ''));
            });
            $clean_items = [];
            foreach ($items as $item) {
                if (!is_array($item)) {
                    continue;
                }
                unset($item['start_ts'], $item['end_ts']);
                $clean_items[] = $item;
            }
            $days[] = [
                'id' => sanitize_key((string) ($day_row['id'] ?? '')),
                'date' => sanitize_text_field((string) ($day_row['date'] ?? '')),
                'title' => sanitize_text_field((string) ($day_row['title'] ?? '')),
                'items' => $clean_items,
            ];
        }

        return $days;
    }


    private function build_event_webapp_attendees($post_id, $limit = 80, $exclude_user_id = 0) {
        $post_id = absint((int) $post_id);
        $limit = max(1, min(300, (int) $limit));
        $exclude_user_id = absint((int) $exclude_user_id);
        if ($post_id <= 0) {
            return [];
        }

        $registry = $this->get_event_webapp_install_registry($post_id);
        if (empty($registry)) {
            return [];
        }

        $indexed = [];
        foreach ($registry as $row) {
            if (!is_array($row)) {
                continue;
            }
            $user_id = absint((int) ($row['user_id'] ?? 0));
            if ($user_id <= 0 || ($exclude_user_id > 0 && $user_id === $exclude_user_id)) {
                continue;
            }
            $created_at_gmt = sanitize_text_field((string) ($row['created_at_gmt'] ?? ''));
            $last_seen_gmt = sanitize_text_field((string) ($row['last_seen_gmt'] ?? ''));
            $created_ts = $created_at_gmt !== '' ? strtotime($created_at_gmt) : false;
            $last_seen_ts = $last_seen_gmt !== '' ? strtotime($last_seen_gmt) : false;

            if (!isset($indexed[$user_id])) {
                $indexed[$user_id] = [
                    'user_id' => $user_id,
                    'install_count' => 0,
                    'created_at_gmt' => $created_at_gmt,
                    'created_ts' => $created_ts ? (int) $created_ts : 0,
                    'last_seen_gmt' => $last_seen_gmt,
                    'last_seen_ts' => $last_seen_ts ? (int) $last_seen_ts : 0,
                ];
            }

            $indexed[$user_id]['install_count'] = (int) $indexed[$user_id]['install_count'] + 1;
            $row_last_seen_ts = $last_seen_ts ? (int) $last_seen_ts : 0;
            $row_created_ts = $created_ts ? (int) $created_ts : 0;

            if ($row_last_seen_ts > (int) $indexed[$user_id]['last_seen_ts']) {
                $indexed[$user_id]['last_seen_ts'] = $row_last_seen_ts;
                $indexed[$user_id]['last_seen_gmt'] = $last_seen_gmt;
            }
            if ($row_created_ts > 0 && (((int) $indexed[$user_id]['created_ts']) <= 0 || $row_created_ts < (int) $indexed[$user_id]['created_ts'])) {
                $indexed[$user_id]['created_ts'] = $row_created_ts;
                $indexed[$user_id]['created_at_gmt'] = $created_at_gmt;
            }
        }

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

        uasort($indexed, static function ($a, $b) {
            $a_seen = absint((int) ($a['last_seen_ts'] ?? 0));
            $b_seen = absint((int) ($b['last_seen_ts'] ?? 0));
            if ($a_seen !== $b_seen) {
                return $b_seen <=> $a_seen;
            }
            return absint((int) ($a['user_id'] ?? 0)) <=> absint((int) ($b['user_id'] ?? 0));
        });

        $indexed = array_slice($indexed, 0, $limit, true);
        $user_ids = array_values(array_unique(array_filter(array_map('absint', array_keys($indexed)))));
        if (empty($user_ids)) {
            return [];
        }

        $users = get_users([
            'include' => $user_ids,
            'fields' => ['ID', 'display_name', 'user_login'],
        ]);
        $name_map = [];
        foreach ((array) $users as $user_row) {
            if (!$user_row instanceof WP_User) {
                continue;
            }
            $name_map[(int) $user_row->ID] = sanitize_text_field((string) ($user_row->display_name ?: $user_row->user_login));
        }

        $now_gmt_ts = absint((int) current_time('timestamp', true));
        $online_window = 10 * MINUTE_IN_SECONDS;
        $rows = [];
        foreach ($indexed as $user_id => $entry) {
            $user_id = absint((int) $user_id);
            if ($user_id <= 0) {
                continue;
            }
            if (!isset($name_map[$user_id])) {
                continue;
            }
            $display_name = (string) $name_map[$user_id];
            $last_seen_ts = absint((int) ($entry['last_seen_ts'] ?? 0));
            $is_online = $last_seen_ts > 0 && $now_gmt_ts > 0 && ($now_gmt_ts - $last_seen_ts) <= $online_window;
            $status = $is_online ? 'online' : 'active';
            $status_label = $is_online
                ? __('Online now', 'nexdirectory')
                : __('Active recently', 'nexdirectory');
            if (!$is_online && $last_seen_ts > 0 && $now_gmt_ts > 0) {
                $status_label = sprintf(
                    __('Active %s ago', 'nexdirectory'),
                    human_time_diff($last_seen_ts, $now_gmt_ts)
                );
            }

            $rows[] = [
                'id' => $user_id,
                'name' => $display_name,
                'avatar' => esc_url_raw((string) get_avatar_url($user_id, ['size' => 120])),
                'status' => $status,
                'status_label' => $status_label,
                'online' => (bool) $is_online,
                'install_count' => absint((int) ($entry['install_count'] ?? 0)),
                'last_seen_gmt' => sanitize_text_field((string) ($entry['last_seen_gmt'] ?? '')),
            ];
        }

        return $rows;
    }


    private function get_event_webapp_itinerary_user_meta_key($post_id) {
        $post_id = absint((int) $post_id);
        return 'eep_webapp_itinerary_' . (string) $post_id;
    }


    private function normalize_event_webapp_saved_session_keys($value) {
        if (is_string($value)) {
            $decoded = json_decode($value, true);
            if (is_array($decoded)) {
                $value = $decoded;
            } else {
                $value = preg_split('/[\s,]+/', $value) ?: [];
            }
        }
        if (!is_array($value)) {
            return [];
        }
        $keys = array_values(array_unique(array_filter(array_map(static function ($entry) {
            return sanitize_key((string) $entry);
        }, $value))));
        return $keys;
    }


    private function normalize_event_webapp_notes_map($value) {
        if (is_string($value)) {
            $decoded = json_decode($value, true);
            if (is_array($decoded)) {
                $value = $decoded;
            } else {
                $value = [];
            }
        }
        if (!is_array($value)) {
            return [];
        }
        $notes = [];
        foreach ($value as $session_key => $note_text) {
            $normalized_key = sanitize_key((string) $session_key);
            if ($normalized_key === '') {
                continue;
            }
            $normalized_note = eep_sanitize_textarea((string) $note_text);
            if (function_exists('mb_strlen')) {
                if (mb_strlen($normalized_note) > 2000) {
                    $normalized_note = (string) mb_substr($normalized_note, 0, 2000);
                }
            } elseif (strlen($normalized_note) > 2000) {
                $normalized_note = (string) substr($normalized_note, 0, 2000);
            }
            if (trim($normalized_note) === '') {
                continue;
            }
            $notes[$normalized_key] = $normalized_note;
        }
        return $notes;
    }


    private function get_event_webapp_user_itinerary($post_id, $user_id) {
        $post_id = absint((int) $post_id);
        $user_id = absint((int) $user_id);
        if ($post_id <= 0 || $user_id <= 0) {
            return [
                'saved_session_keys' => [],
                'notes' => [],
                'updated_at' => '',
            ];
        }

        $meta_key = $this->get_event_webapp_itinerary_user_meta_key($post_id);
        $raw = get_user_meta($user_id, $meta_key, true);
        $decoded = [];
        if (is_array($raw)) {
            $decoded = $raw;
        } elseif (is_string($raw) && trim($raw) !== '') {
            $parsed = json_decode($raw, true);
            if (is_array($parsed)) {
                $decoded = $parsed;
            }
        }

        return [
            'saved_session_keys' => $this->normalize_event_webapp_saved_session_keys($decoded['saved_session_keys'] ?? []),
            'notes' => $this->normalize_event_webapp_notes_map($decoded['notes'] ?? []),
            'updated_at' => sanitize_text_field((string) ($decoded['updated_at'] ?? '')),
        ];
    }


    private function update_event_webapp_user_itinerary($post_id, $user_id, array $saved_session_keys, array $notes) {
        $post_id = absint((int) $post_id);
        $user_id = absint((int) $user_id);
        if ($post_id <= 0 || $user_id <= 0) {
            return;
        }
        $payload = [
            'saved_session_keys' => $this->normalize_event_webapp_saved_session_keys($saved_session_keys),
            'notes' => $this->normalize_event_webapp_notes_map($notes),
            'updated_at' => current_time('mysql', true),
        ];
        update_user_meta($user_id, $this->get_event_webapp_itinerary_user_meta_key($post_id), $payload);
    }


    private function get_event_webapp_chat_rows($post_id) {
        $rows = $this->get_event_webapp_store_rows($post_id, 'eep_webapp_chat_messages');
        $normalized = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $id = sanitize_key((string) ($row['id'] ?? ''));
            if ($id === '') {
                $id = $this->event_webapp_generate_id('chat');
            }
            $body = eep_sanitize_textarea((string) ($row['body'] ?? ''));
            if (trim($body) === '') {
                continue;
            }
            if (function_exists('mb_strlen')) {
                if (mb_strlen($body) > 800) {
                    $body = (string) mb_substr($body, 0, 800);
                }
            } elseif (strlen($body) > 800) {
                $body = (string) substr($body, 0, 800);
            }
            $status = sanitize_key((string) ($row['status'] ?? 'approved'));
            if (!in_array($status, ['approved', 'pending'], true)) {
                $status = 'approved';
            }
            $created_at = sanitize_text_field((string) ($row['created_at_gmt'] ?? ''));
            $created_ts = $created_at !== '' ? strtotime($created_at . ' GMT') : false;
            $normalized[] = [
                'id' => $id,
                'user_id' => absint((int) ($row['user_id'] ?? 0)),
                'user_name' => sanitize_text_field((string) ($row['user_name'] ?? __('Member', 'nexdirectory'))),
                'body' => $body,
                'status' => $status,
                'created_at_gmt' => $created_at !== '' ? $created_at : current_time('mysql', true),
                'created_label' => $created_ts ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), (int) $created_ts) : '',
            ];
        }
        return $normalized;
    }


    private function get_event_webapp_chat_response_rows($post_id, $viewer_user_id = 0, $include_pending = false) {
        $viewer_user_id = absint((int) $viewer_user_id);
        $rows = $this->get_event_webapp_chat_rows($post_id);
        $response = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $status = sanitize_key((string) ($row['status'] ?? 'approved'));
            $row_user_id = absint((int) ($row['user_id'] ?? 0));
            if ($status !== 'approved' && !$include_pending && $row_user_id !== $viewer_user_id) {
                continue;
            }
            $row['is_mine'] = $viewer_user_id > 0 && $row_user_id === $viewer_user_id;
            $response[] = $row;
        }
        return array_values(array_slice($response, -240));
    }


    private function get_event_webapp_qa_rows($post_id) {
        $rows = $this->get_event_webapp_store_rows($post_id, 'eep_webapp_qa_items');
        $normalized = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $id = sanitize_key((string) ($row['id'] ?? ''));
            if ($id === '') {
                $id = $this->event_webapp_generate_id('qa');
            }
            $question = eep_sanitize_textarea((string) ($row['question'] ?? ''));
            if (trim($question) === '') {
                continue;
            }
            if (function_exists('mb_strlen')) {
                if (mb_strlen($question) > 1000) {
                    $question = (string) mb_substr($question, 0, 1000);
                }
            } elseif (strlen($question) > 1000) {
                $question = (string) substr($question, 0, 1000);
            }
            $answer = eep_sanitize_textarea((string) ($row['answer'] ?? ''));
            if (function_exists('mb_strlen')) {
                if (mb_strlen($answer) > 1500) {
                    $answer = (string) mb_substr($answer, 0, 1500);
                }
            } elseif (strlen($answer) > 1500) {
                $answer = (string) substr($answer, 0, 1500);
            }
            $voters = array_values(array_unique(array_filter(array_map('absint', (array) ($row['voters'] ?? [])))));
            $status = sanitize_key((string) ($row['status'] ?? 'open'));
            if (!in_array($status, ['open', 'hidden'], true)) {
                $status = 'open';
            }
            $created_at = sanitize_text_field((string) ($row['created_at_gmt'] ?? ''));
            $created_ts = $created_at !== '' ? strtotime($created_at . ' GMT') : false;
            $answered_at = sanitize_text_field((string) ($row['answered_at_gmt'] ?? ''));
            $answered_ts = $answered_at !== '' ? strtotime($answered_at . ' GMT') : false;
            $normalized[] = [
                'id' => $id,
                'question' => $question,
                'user_id' => absint((int) ($row['user_id'] ?? 0)),
                'user_name' => sanitize_text_field((string) ($row['user_name'] ?? __('Member', 'nexdirectory'))),
                'answer' => $answer,
                'answer_user_id' => absint((int) ($row['answer_user_id'] ?? 0)),
                'answer_user_name' => sanitize_text_field((string) ($row['answer_user_name'] ?? '')),
                'voters' => $voters,
                'status' => $status,
                'created_at_gmt' => $created_at !== '' ? $created_at : current_time('mysql', true),
                'created_label' => $created_ts ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), (int) $created_ts) : '',
                'answered_at_gmt' => $answered_at,
                'answered_label' => $answered_ts ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), (int) $answered_ts) : '',
            ];
        }
        return $normalized;
    }


    private function get_event_webapp_qa_response_rows($post_id, $viewer_user_id = 0, $include_hidden = false) {
        $viewer_user_id = absint((int) $viewer_user_id);
        $rows = $this->get_event_webapp_qa_rows($post_id);
        $response = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $status = sanitize_key((string) ($row['status'] ?? 'open'));
            $row_user_id = absint((int) ($row['user_id'] ?? 0));
            if ($status === 'hidden' && !$include_hidden && !($viewer_user_id > 0 && $row_user_id === $viewer_user_id)) {
                continue;
            }
            $voters = array_values(array_unique(array_filter(array_map('absint', (array) ($row['voters'] ?? [])))));
            $row['vote_count'] = count($voters);
            $row['has_voted'] = $viewer_user_id > 0 && in_array($viewer_user_id, $voters, true);
            unset($row['voters']);
            $response[] = $row;
        }

        usort($response, static function ($a, $b) {
            $a_votes = absint((int) ($a['vote_count'] ?? 0));
            $b_votes = absint((int) ($b['vote_count'] ?? 0));
            if ($a_votes !== $b_votes) {
                return $b_votes <=> $a_votes;
            }
            $a_ts = strtotime((string) ($a['created_at_gmt'] ?? ''));
            $b_ts = strtotime((string) ($b['created_at_gmt'] ?? ''));
            if ($a_ts !== $b_ts) {
                return $b_ts <=> $a_ts;
            }
            return strcasecmp((string) ($a['id'] ?? ''), (string) ($b['id'] ?? ''));
        });

        return array_values(array_slice($response, 0, 240));
    }


    private function get_event_webapp_poll_rows($post_id) {
        $rows = $this->get_event_webapp_store_rows($post_id, 'eep_webapp_polls_feed');
        $normalized = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $id = sanitize_key((string) ($row['id'] ?? ''));
            if ($id === '') {
                $id = $this->event_webapp_generate_id('poll');
            }
            $question = eep_sanitize_textarea((string) ($row['question'] ?? ''));
            $question = trim((string) $question);
            if ($question === '') {
                continue;
            }
            if (function_exists('mb_strlen')) {
                if (mb_strlen($question) > 300) {
                    $question = (string) mb_substr($question, 0, 300);
                }
            } elseif (strlen($question) > 300) {
                $question = (string) substr($question, 0, 300);
            }
            $status = sanitize_key((string) ($row['status'] ?? 'open'));
            if (!in_array($status, ['open', 'closed', 'hidden'], true)) {
                $status = 'open';
            }
            $options = [];
            foreach ((array) ($row['options'] ?? []) as $index => $option_row) {
                if (!is_array($option_row)) {
                    continue;
                }
                $option_id = sanitize_key((string) ($option_row['id'] ?? ''));
                if ($option_id === '') {
                    $option_id = sanitize_key($id . '-opt-' . ((int) $index + 1));
                }
                $label = sanitize_text_field((string) ($option_row['label'] ?? ''));
                if ($label === '') {
                    continue;
                }
                if (function_exists('mb_strlen')) {
                    if (mb_strlen($label) > 120) {
                        $label = (string) mb_substr($label, 0, 120);
                    }
                } elseif (strlen($label) > 120) {
                    $label = (string) substr($label, 0, 120);
                }
                $voters = array_values(array_unique(array_filter(array_map('absint', (array) ($option_row['voters'] ?? [])))));
                $options[] = [
                    'id' => $option_id,
                    'label' => $label,
                    'voters' => $voters,
                ];
            }
            if (count($options) < 2) {
                continue;
            }
            $created_at = sanitize_text_field((string) ($row['created_at_gmt'] ?? ''));
            $created_ts = $created_at !== '' ? strtotime($created_at . ' GMT') : false;
            $normalized[] = [
                'id' => $id,
                'question' => $question,
                'status' => $status,
                'multiple' => !empty($row['multiple']),
                'options' => $options,
                'created_by_user_id' => absint((int) ($row['created_by_user_id'] ?? 0)),
                'created_by_name' => sanitize_text_field((string) ($row['created_by_name'] ?? __('Event Team', 'nexdirectory'))),
                'created_at_gmt' => $created_at !== '' ? $created_at : current_time('mysql', true),
                'created_label' => $created_ts ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), (int) $created_ts) : '',
            ];
        }

        usort($normalized, static function ($a, $b) {
            $a_ts = strtotime((string) ($a['created_at_gmt'] ?? ''));
            $b_ts = strtotime((string) ($b['created_at_gmt'] ?? ''));
            if ($a_ts !== $b_ts) {
                return $b_ts <=> $a_ts;
            }
            return strcasecmp((string) ($a['id'] ?? ''), (string) ($b['id'] ?? ''));
        });

        return array_values(array_slice($normalized, 0, 200));
    }


    private function get_event_webapp_poll_response_rows($post_id, $viewer_user_id = 0, $include_hidden = false, $include_voters = false) {
        $viewer_user_id = absint((int) $viewer_user_id);
        $rows = $this->get_event_webapp_poll_rows($post_id);
        $response = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $status = sanitize_key((string) ($row['status'] ?? 'open'));
            if ($status === 'hidden' && !$include_hidden) {
                continue;
            }

            $selected_option_ids = [];
            $options = [];
            $total_votes = 0;
            foreach ((array) ($row['options'] ?? []) as $option_row) {
                if (!is_array($option_row)) {
                    continue;
                }
                $voters = array_values(array_unique(array_filter(array_map('absint', (array) ($option_row['voters'] ?? [])))));
                $vote_count = count($voters);
                $total_votes += $vote_count;
                if ($viewer_user_id > 0 && in_array($viewer_user_id, $voters, true)) {
                    $selected_option_ids[] = sanitize_key((string) ($option_row['id'] ?? ''));
                }
                $response_option = [
                    'id' => sanitize_key((string) ($option_row['id'] ?? '')),
                    'label' => sanitize_text_field((string) ($option_row['label'] ?? '')),
                    'vote_count' => $vote_count,
                ];
                if ($include_voters) {
                    $response_option['voters'] = $voters;
                }
                $options[] = $response_option;
            }

            if (count($options) < 2) {
                continue;
            }

            $response[] = [
                'id' => sanitize_key((string) ($row['id'] ?? '')),
                'question' => eep_sanitize_textarea((string) ($row['question'] ?? '')),
                'status' => $status,
                'multiple' => !empty($row['multiple']),
                'options' => $options,
                'selected_option_ids' => array_values(array_unique(array_filter(array_map('sanitize_key', $selected_option_ids)))),
                'has_voted' => !empty($selected_option_ids),
                'total_votes' => $total_votes,
                'created_by_user_id' => absint((int) ($row['created_by_user_id'] ?? 0)),
                'created_by_name' => sanitize_text_field((string) ($row['created_by_name'] ?? __('Event Team', 'nexdirectory'))),
                'created_at_gmt' => sanitize_text_field((string) ($row['created_at_gmt'] ?? '')),
                'created_label' => sanitize_text_field((string) ($row['created_label'] ?? '')),
            ];
        }
        return array_values(array_slice($response, 0, 160));
    }


    private function get_event_webapp_notification_rows($post_id) {
        $rows = $this->get_event_webapp_store_rows($post_id, 'eep_webapp_notifications_feed');
        $normalized = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $id = sanitize_key((string) ($row['id'] ?? ''));
            if ($id === '') {
                $id = $this->event_webapp_generate_id('notif');
            }
            $text = eep_sanitize_textarea((string) ($row['text'] ?? ''));
            if (trim($text) === '') {
                continue;
            }
            if (function_exists('mb_strlen')) {
                if (mb_strlen($text) > 400) {
                    $text = (string) mb_substr($text, 0, 400);
                }
            } elseif (strlen($text) > 400) {
                $text = (string) substr($text, 0, 400);
            }
            $type = sanitize_key((string) ($row['type'] ?? 'info'));
            if (!in_array($type, ['info', 'success', 'warning', 'alert'], true)) {
                $type = 'info';
            }
            $created_at = sanitize_text_field((string) ($row['created_at_gmt'] ?? ''));
            $created_ts = $created_at !== '' ? strtotime($created_at . ' GMT') : false;
            $normalized[] = [
                'id' => $id,
                'text' => $text,
                'type' => $type,
                'author_id' => absint((int) ($row['author_id'] ?? 0)),
                'author_name' => sanitize_text_field((string) ($row['author_name'] ?? __('Event Team', 'nexdirectory'))),
                'created_at_gmt' => $created_at !== '' ? $created_at : current_time('mysql', true),
                'time' => $created_ts ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), (int) $created_ts) : '',
            ];
        }

        usort($normalized, static function ($a, $b) {
            $a_ts = strtotime((string) ($a['created_at_gmt'] ?? ''));
            $b_ts = strtotime((string) ($b['created_at_gmt'] ?? ''));
            if ($a_ts !== $b_ts) {
                return $b_ts <=> $a_ts;
            }
            return strcasecmp((string) ($a['id'] ?? ''), (string) ($b['id'] ?? ''));
        });

        return array_values(array_slice($normalized, 0, 200));
    }


    private function build_event_webapp_bootstrap_payload($post_id, $current_user_id = 0, $can_manage = false, array $settings = [], array $auth_identity = []) {
        $post_id = absint((int) $post_id);
        $current_user_id = absint((int) $current_user_id);
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return new WP_Error('eep_webapp_invalid_event', __('Invalid event selected.', 'nexdirectory'), ['status' => 400]);
        }
        if (empty($settings)) {
            $settings = $this->get_event_webapp_settings($post_id);
        }

        $title = sanitize_text_field((string) get_the_title($post_id));
        $excerpt = eep_sanitize_textarea((string) get_post_field('post_excerpt', $post_id));
        if ($excerpt === '') {
            $excerpt = wp_trim_words(eep_strip_all_tags((string) get_post_field('post_content', $post_id)), 24);
        }
        $event_title = $settings['title'] !== '' ? (string) $settings['title'] : $title;
        $event_subtitle = $settings['subtitle'] !== '' ? (string) $settings['subtitle'] : $excerpt;
        $start_raw = sanitize_text_field((string) get_post_meta($post_id, 'eep_start', true));
        $end_raw = sanitize_text_field((string) get_post_meta($post_id, 'eep_end', true));
        $start_ts = $start_raw !== '' ? strtotime($start_raw) : false;
        $end_ts = $end_raw !== '' ? strtotime($end_raw) : false;
        $now_ts = (int) current_time('timestamp');
        $is_live_now = false;
        if ($start_ts) {
            $live_end_ts = ($end_ts && (int) $end_ts >= (int) $start_ts) ? (int) $end_ts : ((int) $start_ts + DAY_IN_SECONDS);
            $is_live_now = $now_ts >= (int) $start_ts && $now_ts <= $live_end_ts;
        }
        $date_label = (string) $this->format_event_date($post_id);
        if ($date_label === '') {
            if ($start_ts) {
                $date_label = wp_date(get_option('date_format'), (int) $start_ts);
            } else {
                $date_label = __('Date TBD', 'nexdirectory');
            }
        }
        $timezone = sanitize_text_field((string) get_post_meta($post_id, 'eep_timezone', true));
        if ($timezone === '') {
            $timezone = (string) get_option('timezone_string', '');
        }

        $venue = sanitize_text_field((string) get_post_meta($post_id, 'eep_venue', true));
        $address = sanitize_text_field((string) get_post_meta($post_id, 'eep_address', true));
        $city = sanitize_text_field((string) get_post_meta($post_id, 'eep_city', true));
        $country = sanitize_text_field((string) get_post_meta($post_id, 'eep_country', true));
        $location_parts = array_filter([$venue, $address, $city, $country], static function ($part) {
            return trim((string) $part) !== '';
        });
        $location = implode(', ', $location_parts);
        $map_link = $location !== '' ? esc_url_raw('https://maps.google.com/?q=' . rawurlencode($location)) : '';
        $map_embed_url = $location !== '' ? esc_url_raw('https://maps.google.com/maps?q=' . rawurlencode($location) . '&z=14&output=embed') : '';

        $cover_image = esc_url_raw((string) get_post_meta($post_id, 'eep_cover_image_url', true));
        if ($cover_image === '') {
            $cover_image = esc_url_raw((string) $this->get_default_image_url('hero'));
        }

        $session_rows = $this->build_event_webapp_session_rows($post_id);
        $day_rows = $this->build_event_webapp_day_groups($session_rows);
        $auth_source = sanitize_key((string) ($auth_identity['source'] ?? ($current_user_id > 0 ? 'session' : 'guest')));
        if (!in_array($auth_source, ['guest', 'session', 'install'], true)) {
            $auth_source = $current_user_id > 0 ? 'session' : 'guest';
        }
        $session_user_id = is_user_logged_in() ? absint((int) get_current_user_id()) : 0;
        $is_session_user = $session_user_id > 0 && $current_user_id > 0 && $session_user_id === $current_user_id && $auth_source === 'session';
        $itinerary = $is_session_user
            ? $this->get_event_webapp_user_itinerary($post_id, $current_user_id)
            : ['saved_session_keys' => [], 'notes' => [], 'updated_at' => '', 'requires_login' => true];
        $can_moderate_app = $this->event_webapp_user_can_moderate_chat($post_id, $current_user_id, $settings, $can_manage);
        $chat_rows = $this->get_event_webapp_chat_response_rows($post_id, $current_user_id, !empty($can_moderate_app));
        $qa_rows = $this->get_event_webapp_qa_response_rows($post_id, $current_user_id, !empty($can_moderate_app));
        $poll_rows = $this->get_event_webapp_poll_response_rows($post_id, $current_user_id, !empty($can_moderate_app), !empty($can_moderate_app));
        $notification_rows = $this->get_event_webapp_notification_rows($post_id);
        if (empty($notification_rows)) {
            $notification_rows[] = [
                'id' => $this->event_webapp_generate_id('notif'),
                'text' => sprintf(__('Welcome to %s.', 'nexdirectory'), $event_title),
                'type' => 'info',
                'author_id' => 0,
                'author_name' => __('System', 'nexdirectory'),
                'created_at_gmt' => current_time('mysql', true),
                'time' => wp_date(get_option('date_format') . ' ' . get_option('time_format')),
            ];
        }
        $attendees = !empty($settings['enable_attendees']) ? $this->build_event_webapp_attendees($post_id, 120, $current_user_id) : [];
        $calendar_url = esc_url_raw((string) eep_add_query_arg('eep_ics', $post_id, home_url('/')));
        $google_calendar_url = '';
        if ($start_ts) {
            $end_for_calendar = $end_ts && $end_ts > $start_ts ? $end_ts : ((int) $start_ts + HOUR_IN_SECONDS);
            $google_calendar_url = esc_url_raw((string) eep_add_query_arg([
                'action' => 'TEMPLATE',
                'text' => $title,
                'dates' => gmdate('Ymd\THis\Z', (int) $start_ts) . '/' . gmdate('Ymd\THis\Z', (int) $end_for_calendar),
                'details' => eep_strip_all_tags($excerpt),
                'location' => $location,
            ], 'https://calendar.google.com/calendar/render'));
        }
        $user_display_name = __('Attendee', 'nexdirectory');
        if ($current_user_id > 0) {
            $current_user = get_user_by('id', $current_user_id);
            if ($current_user instanceof WP_User) {
                $user_display_name = sanitize_text_field((string) ($current_user->display_name ?: $current_user->user_login));
            }
        }
        $install_id = sanitize_key((string) ($auth_identity['install_id'] ?? ''));

        return [
            'event' => [
                'id' => $post_id,
                'title' => $event_title,
                'subtitle' => $event_subtitle,
                'date_label' => $date_label,
                'start_raw' => $start_raw,
                'end_raw' => $end_raw,
                'is_live_now' => (bool) $is_live_now,
                'timezone' => $timezone,
                'location' => $location,
                'cover_image' => $cover_image,
                'icon_url' => esc_url_raw((string) ($settings['icon_url'] ?? '')),
                'splash_url' => esc_url_raw((string) ($settings['splash_url'] ?? '')),
                'header_logo_url' => esc_url_raw((string) ($settings['header_logo_url'] ?? '')),
                'loading_bg_color' => sanitize_hex_color((string) ($settings['loading_bg_color'] ?? '')) ?: '#ffffff',
                'accent_color' => sanitize_hex_color((string) ($settings['accent_color'] ?? '')) ?: '#4F46E5',
                'map_link' => $map_link,
                'map_embed_url' => $map_embed_url,
                'calendar_url' => $calendar_url,
                'google_calendar_url' => $google_calendar_url,
                'permalink' => esc_url_raw((string) get_permalink($post_id)),
                'assets' => array_values(array_unique(array_filter(array_map('esc_url_raw', (array) ($settings['asset_urls'] ?? []))))),
            ],
            'settings' => [
                'enabled' => !empty($settings['enabled']),
                'enable_chat' => !empty($settings['enable_chat']),
                'enable_qa' => !empty($settings['enable_qa']),
                'enable_polls' => !empty($settings['enable_polls']),
                'enable_map' => !empty($settings['enable_map']),
                'enable_notifications' => !empty($settings['enable_notifications']),
                'enable_attendees' => !empty($settings['enable_attendees']),
                'enable_custom_itinerary' => !empty($settings['enable_custom_itinerary']),
                'requires_login' => !empty($settings['requires_login']),
                'push_enabled' => !empty($settings['push_enabled']),
                'push_provider' => sanitize_key((string) ($settings['push_provider'] ?? 'none')),
                'push_key' => !empty($can_manage) ? sanitize_text_field((string) ($settings['push_key'] ?? '')) : '',
                'chat_moderation' => sanitize_key((string) ($settings['chat_moderation'] ?? 'open')),
                'qa_moderation' => sanitize_key((string) ($settings['qa_moderation'] ?? 'open')),
                'qa_allow_attendee_answers' => !empty($settings['qa_allow_attendee_answers']),
                'qa_allow_anonymous' => !empty($settings['qa_allow_anonymous']),
                'polls_vote_mode' => sanitize_key((string) ($settings['polls_vote_mode'] ?? 'single')),
            ],
            'days' => $day_rows,
            'chat' => [
                'messages' => $chat_rows,
            ],
            'qa' => [
                'items' => $qa_rows,
            ],
            'polls' => [
                'items' => $poll_rows,
            ],
            'notifications' => $notification_rows,
            'attendees' => $attendees,
            'itinerary' => $itinerary,
            'user' => [
                'id' => $current_user_id,
                'logged_in' => $is_session_user,
                'display_name' => $user_display_name,
                'can_manage' => !empty($can_manage) || !empty($can_moderate_app),
                'can_moderate_chat' => !empty($can_moderate_app),
                'auth_source' => $auth_source,
                'install_id' => $install_id,
            ],
        ];
    }


    private function get_event_webapp_request_context($request, $require_enabled = true) {
        $post_id = absint((int) $request->get_param('event_id'));
        if ($post_id <= 0) {
            $post_id = absint((int) ($_GET['event_id'] ?? 0)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        }
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return new WP_Error('eep_webapp_invalid_event', __('Invalid event selected.', 'nexdirectory'), ['status' => 400]);
        }

        $auth_identity = $this->resolve_event_webapp_auth_identity($post_id, $request, true);
        $current_user_id = absint((int) ($auth_identity['user_id'] ?? 0));
        $can_manage = $this->event_webapp_user_can_manage_event($post_id, $current_user_id);
        $post_status = (string) get_post_status($post_id);
        if ($post_status !== 'publish' && !$can_manage) {
            return new WP_Error('eep_webapp_not_available', __('This event app is not available.', 'nexdirectory'), ['status' => 403]);
        }

        $settings = $this->get_event_webapp_settings($post_id);
        if ($require_enabled && empty($settings['enabled']) && !$can_manage) {
            return new WP_Error('eep_webapp_disabled', __('The event organizer has not enabled the web app.', 'nexdirectory'), ['status' => 403]);
        }
        if (!$this->event_webapp_user_can_access_event($post_id, $current_user_id, $settings, $can_manage)) {
            if ($current_user_id <= 0 && !empty($settings['requires_login'])) {
                return new WP_Error('eep_webapp_login_required', __('You must be logged in to access this event app.', 'nexdirectory'), ['status' => 401]);
            }
            return new WP_Error('eep_webapp_forbidden', __('You do not have access to this event app.', 'nexdirectory'), ['status' => 403]);
        }

        $can_moderate_chat = $this->event_webapp_user_can_moderate_chat($post_id, $current_user_id, $settings, $can_manage);

        return [
            'post_id' => $post_id,
            'user_id' => $current_user_id,
            'can_manage' => $can_manage,
            'can_moderate_chat' => $can_moderate_chat,
            'settings' => $settings,
            'auth_identity' => $auth_identity,
        ];
    }


    private function event_webapp_user_has_install($post_id, $user_id) {
        $post_id = absint((int) $post_id);
        $user_id = absint((int) $user_id);
        if ($post_id <= 0 || $user_id <= 0) {
            return false;
        }

        $registry = $this->get_event_webapp_install_registry($post_id);
        foreach ($registry as $row) {
            if (!is_array($row)) {
                continue;
            }
            if (absint((int) ($row['user_id'] ?? 0)) === $user_id) {
                return true;
            }
        }

        return false;
    }


    private function get_event_webapp_dm_response_rows($thread_id, $user_id, $limit = 200) {
        $thread_id = absint((int) $thread_id);
        $user_id = absint((int) $user_id);
        $limit = max(1, min(500, (int) $limit));
        if ($thread_id <= 0 || $user_id <= 0) {
            return [];
        }

        $raw_messages = $this->get_inbox_messages_for_thread($thread_id, $limit);
        if (empty($raw_messages)) {
            return [];
        }

        $rows = [];
        foreach ((array) $raw_messages as $raw_message) {
            if (!is_array($raw_message)) {
                continue;
            }
            $message_id = absint((int) ($raw_message['id'] ?? 0));
            if ($message_id <= 0) {
                continue;
            }
            $sender_id = absint((int) ($raw_message['sender_id'] ?? 0));
            $recipient_id = absint((int) ($raw_message['recipient_id'] ?? 0));
            $created_at = sanitize_text_field((string) ($raw_message['created_at'] ?? ''));
            $created_ts = $created_at !== '' ? strtotime($created_at) : false;
            $rows[] = [
                'id' => $message_id,
                'sender_id' => $sender_id,
                'recipient_id' => $recipient_id,
                'text' => eep_sanitize_textarea((string) ($raw_message['body'] ?? '')),
                'subject' => sanitize_text_field((string) ($raw_message['subject'] ?? '')),
                'created_at_gmt' => $created_at,
                'created_label' => $created_ts ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), (int) $created_ts) : '',
                'time' => $created_ts ? wp_date(get_option('time_format'), (int) $created_ts) : '',
                'is_mine' => $sender_id === $user_id,
            ];
        }

        return $rows;
    }


    public function rest_event_webapp_bootstrap($request) {
        $context = $this->get_event_webapp_request_context($request, true);
        if (is_wp_error($context)) {
            return $context;
        }

        $payload = $this->build_event_webapp_bootstrap_payload(
            (int) ($context['post_id'] ?? 0),
            (int) ($context['user_id'] ?? 0),
            !empty($context['can_manage']),
            is_array($context['settings'] ?? null) ? (array) $context['settings'] : [],
            is_array($context['auth_identity'] ?? null) ? (array) $context['auth_identity'] : []
        );
        if (is_wp_error($payload)) {
            return $payload;
        }

        return rest_ensure_response($payload);
    }


    public function rest_event_webapp_chat($request) {
        $context = $this->get_event_webapp_request_context($request, true);
        if (is_wp_error($context)) {
            return $context;
        }
        $post_id = absint((int) ($context['post_id'] ?? 0));
        $user_id = absint((int) ($context['user_id'] ?? 0));
        $settings = is_array($context['settings'] ?? null) ? (array) $context['settings'] : [];
        $can_moderate = !empty($context['can_moderate_chat']);

        if ($request->get_method() === 'GET') {
            return rest_ensure_response([
                'messages' => $this->get_event_webapp_chat_response_rows($post_id, $user_id, $can_moderate),
                'chat_moderation' => sanitize_key((string) ($settings['chat_moderation'] ?? 'open')),
                'can_moderate' => $can_moderate,
                'can_post' => $user_id > 0 && !empty($settings['enable_chat']),
            ]);
        }

        if (empty($settings['enable_chat']) && empty($context['can_manage'])) {
            return new WP_Error('eep_webapp_chat_disabled', __('Chat is disabled for this event.', 'nexdirectory'), ['status' => 403]);
        }

        $action = sanitize_key((string) $request->get_param('action'));
        $rows = $this->get_event_webapp_chat_rows($post_id);

        if (in_array($action, ['approve', 'delete'], true)) {
            if (!$can_moderate) {
                return new WP_Error('eep_webapp_chat_forbidden', __('Only moderators can manage chat messages.', 'nexdirectory'), ['status' => 403]);
            }
            $message_id = sanitize_key((string) $request->get_param('message_id'));
            if ($message_id === '') {
                return new WP_Error('eep_webapp_chat_missing_message', __('Choose a message first.', 'nexdirectory'), ['status' => 400]);
            }
            $updated = [];
            foreach ($rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $row_id = sanitize_key((string) ($row['id'] ?? ''));
                if ($row_id === $message_id) {
                    if ($action === 'delete') {
                        continue;
                    }
                    $row['status'] = 'approved';
                }
                $updated[] = $row;
            }
            $this->update_event_webapp_store_rows($post_id, 'eep_webapp_chat_messages', $updated, 500);
            return rest_ensure_response([
                'messages' => $this->get_event_webapp_chat_response_rows($post_id, $user_id, $can_moderate),
                'can_moderate' => $can_moderate,
            ]);
        }

        if ($user_id <= 0) {
            return new WP_Error('eep_webapp_chat_login', __('Please log in to send a message.', 'nexdirectory'), ['status' => 403]);
        }

        $chat_moderation = sanitize_key((string) ($settings['chat_moderation'] ?? 'open'));
        if ($chat_moderation === 'admins-only' && !$can_moderate) {
            return new WP_Error('eep_webapp_chat_restricted', __('Only event moderators can post in this chat.', 'nexdirectory'), ['status' => 403]);
        }

        $body = eep_sanitize_textarea((string) $request->get_param('message'));
        if (trim($body) === '') {
            return new WP_Error('eep_webapp_chat_empty', __('Type a message first.', 'nexdirectory'), ['status' => 400]);
        }
        if (function_exists('mb_strlen')) {
            if (mb_strlen($body) > 800) {
                $body = (string) mb_substr($body, 0, 800);
            }
        } elseif (strlen($body) > 800) {
            $body = (string) substr($body, 0, 800);
        }

        $sender = get_user_by('id', $user_id);
        $sender_name = $sender instanceof WP_User
            ? sanitize_text_field((string) ($sender->display_name ?: $sender->user_login))
            : __('Member', 'nexdirectory');
        $status = ($chat_moderation === 'moderated' && !$can_moderate) ? 'pending' : 'approved';
        $rows[] = [
            'id' => $this->event_webapp_generate_id('chat'),
            'user_id' => $user_id,
            'user_name' => $sender_name,
            'body' => $body,
            'status' => $status,
            'created_at_gmt' => current_time('mysql', true),
        ];
        $this->update_event_webapp_store_rows($post_id, 'eep_webapp_chat_messages', $rows, 500);

        return rest_ensure_response([
            'message_status' => $status,
            'messages' => $this->get_event_webapp_chat_response_rows($post_id, $user_id, $can_moderate),
            'can_moderate' => $can_moderate,
        ]);
    }


    public function rest_event_webapp_dm($request) {
        $context = $this->get_event_webapp_request_context($request, true);
        if (is_wp_error($context)) {
            return $context;
        }

        $post_id = absint((int) ($context['post_id'] ?? 0));
        $user_id = absint((int) ($context['user_id'] ?? 0));
        $settings = is_array($context['settings'] ?? null) ? (array) $context['settings'] : [];
        $can_manage = !empty($context['can_manage']) || !empty($context['can_moderate_chat']);

        if (empty($settings['enable_attendees']) && !$can_manage) {
            return new WP_Error('eep_webapp_dm_disabled', __('Directory messaging is disabled for this event.', 'nexdirectory'), ['status' => 403]);
        }
        if ($user_id <= 0) {
            return new WP_Error('eep_webapp_dm_login', __('Please log in to message attendees.', 'nexdirectory'), ['status' => 403]);
        }

        $recipient_id = absint((int) $request->get_param('recipient_id'));
        if ($recipient_id <= 0) {
            return new WP_Error('eep_webapp_dm_missing_recipient', __('Choose an attendee first.', 'nexdirectory'), ['status' => 400]);
        }
        if ($recipient_id === $user_id) {
            return new WP_Error('eep_webapp_dm_self', __('You cannot message yourself.', 'nexdirectory'), ['status' => 400]);
        }

        $recipient = get_user_by('id', $recipient_id);
        if (!$recipient instanceof WP_User) {
            return new WP_Error('eep_webapp_dm_invalid_recipient', __('Selected attendee is not available.', 'nexdirectory'), ['status' => 404]);
        }

        if (!$this->event_webapp_user_has_install($post_id, $recipient_id)) {
            return new WP_Error('eep_webapp_dm_not_installed', __('This attendee has not installed the event app yet.', 'nexdirectory'), ['status' => 403]);
        }

        $recipient_name = sanitize_text_field((string) ($recipient->display_name ?: $recipient->user_login));
        $recipient_row = [
            'id' => $recipient_id,
            'name' => $recipient_name !== '' ? $recipient_name : __('Member', 'nexdirectory'),
            'avatar' => esc_url_raw((string) get_avatar_url($recipient_id, ['size' => 120])),
        ];

        $thread_id = $this->get_inbox_thread_id_for_participants($post_id, $user_id, $recipient_id);
        if ($request->get_method() === 'GET') {
            if ($thread_id > 0) {
                $this->mark_inbox_thread_read_for_user($thread_id, $user_id);
            }
            return rest_ensure_response([
                'thread_id' => $thread_id,
                'recipient' => $recipient_row,
                'messages' => $thread_id > 0 ? $this->get_event_webapp_dm_response_rows($thread_id, $user_id, 240) : [],
            ]);
        }

        $action = sanitize_key((string) $request->get_param('action'));
        if ($action === '') {
            $action = 'send';
        }
        if ($action === 'thread') {
            if ($thread_id > 0) {
                $this->mark_inbox_thread_read_for_user($thread_id, $user_id);
            }
            return rest_ensure_response([
                'thread_id' => $thread_id,
                'recipient' => $recipient_row,
                'messages' => $thread_id > 0 ? $this->get_event_webapp_dm_response_rows($thread_id, $user_id, 240) : [],
            ]);
        }
        if ($action !== 'send') {
            return new WP_Error('eep_webapp_dm_invalid_action', __('Unsupported direct message action.', 'nexdirectory'), ['status' => 400]);
        }

        $message = eep_sanitize_textarea((string) $request->get_param('message'));
        if (trim((string) $message) === '') {
            return new WP_Error('eep_webapp_dm_empty', __('Type a message first.', 'nexdirectory'), ['status' => 400]);
        }
        if (function_exists('mb_strlen')) {
            if (mb_strlen($message) > 1500) {
                $message = (string) mb_substr($message, 0, 1500);
            }
        } elseif (strlen($message) > 1500) {
            $message = (string) substr($message, 0, 1500);
        }

        $subject = sanitize_text_field((string) $request->get_param('subject'));
        if ($subject === '') {
            $subject = sprintf(__('Message about %s', 'nexdirectory'), get_the_title($post_id));
        }

        $send_result = $this->inbox_send_message($post_id, $user_id, $recipient_id, $subject, $message);
        if (is_wp_error($send_result)) {
            return $send_result;
        }

        $thread_id = absint((int) ($send_result['thread_id'] ?? 0));
        $message_id = absint((int) ($send_result['message_id'] ?? 0));
        if ($thread_id > 0) {
            $this->mark_inbox_thread_read_for_user($thread_id, $user_id);
        }
        if ($thread_id > 0 && $message_id > 0) {
            $this->send_inbox_message_notification_email($recipient_id, $user_id, $post_id, $thread_id, $message_id, $subject);
        }

        return rest_ensure_response([
            'thread_id' => $thread_id,
            'recipient' => $recipient_row,
            'sent_message_id' => $message_id,
            'messages' => $thread_id > 0 ? $this->get_event_webapp_dm_response_rows($thread_id, $user_id, 240) : [],
        ]);
    }


    public function rest_event_webapp_qa($request) {
        $context = $this->get_event_webapp_request_context($request, true);
        if (is_wp_error($context)) {
            return $context;
        }
        $post_id = absint((int) ($context['post_id'] ?? 0));
        $user_id = absint((int) ($context['user_id'] ?? 0));
        $can_manage = !empty($context['can_manage']) || !empty($context['can_moderate_chat']);
        $settings = is_array($context['settings'] ?? null) ? (array) $context['settings'] : [];
        $qa_moderation = sanitize_key((string) ($settings['qa_moderation'] ?? 'open'));
        if (!in_array($qa_moderation, ['open', 'moderated', 'admins-only'], true)) {
            $qa_moderation = 'open';
        }
        $qa_allow_anonymous = !empty($settings['qa_allow_anonymous']);
        $qa_allow_attendee_answers = !empty($settings['qa_allow_attendee_answers']);
        $can_ask = !empty($settings['enable_qa']) && ($user_id > 0 || $qa_allow_anonymous || $can_manage);
        if ($qa_moderation === 'admins-only' && !$can_manage) {
            $can_ask = false;
        }

        if ($request->get_method() === 'GET') {
            return rest_ensure_response([
                'items' => $this->get_event_webapp_qa_response_rows($post_id, $user_id, $can_manage),
                'can_moderate' => $can_manage,
                'can_ask' => $can_ask,
                'qa_moderation' => $qa_moderation,
                'allow_attendee_answers' => $qa_allow_attendee_answers,
                'allow_anonymous' => $qa_allow_anonymous,
            ]);
        }

        if (empty($settings['enable_qa']) && !$can_manage) {
            return new WP_Error('eep_webapp_qa_disabled', __('Q&A is disabled for this event.', 'nexdirectory'), ['status' => 403]);
        }

        $action = sanitize_key((string) $request->get_param('action'));
        if ($action === '') {
            $action = 'ask';
        }
        $rows = $this->get_event_webapp_qa_rows($post_id);

        if ($action === 'ask') {
            if ($qa_moderation === 'admins-only' && !$can_manage) {
                return new WP_Error('eep_webapp_qa_restricted', __('Only event moderators can post Q&A items for this event.', 'nexdirectory'), ['status' => 403]);
            }
            if ($user_id <= 0 && !$qa_allow_anonymous && !$can_manage) {
                return new WP_Error('eep_webapp_qa_login', __('Please log in to ask a question.', 'nexdirectory'), ['status' => 403]);
            }
            $question = eep_sanitize_textarea((string) $request->get_param('question'));
            if (trim($question) === '') {
                return new WP_Error('eep_webapp_qa_empty', __('Type a question first.', 'nexdirectory'), ['status' => 400]);
            }
            if (function_exists('mb_strlen')) {
                if (mb_strlen($question) > 1000) {
                    $question = (string) mb_substr($question, 0, 1000);
                }
            } elseif (strlen($question) > 1000) {
                $question = (string) substr($question, 0, 1000);
            }
            $asker = $user_id > 0 ? get_user_by('id', $user_id) : null;
            $asker_name = $asker instanceof WP_User
                ? sanitize_text_field((string) ($asker->display_name ?: $asker->user_login))
                : ($qa_allow_anonymous ? __('Anonymous', 'nexdirectory') : __('Member', 'nexdirectory'));
            $question_status = ($qa_moderation === 'moderated' && !$can_manage) ? 'hidden' : 'open';
            $rows[] = [
                'id' => $this->event_webapp_generate_id('qa'),
                'question' => $question,
                'user_id' => $user_id,
                'user_name' => $asker_name,
                'answer' => '',
                'answer_user_id' => 0,
                'answer_user_name' => '',
                'voters' => [],
                'status' => $question_status,
                'created_at_gmt' => current_time('mysql', true),
                'answered_at_gmt' => '',
            ];
        } elseif ($action === 'vote') {
            if ($user_id <= 0) {
                return new WP_Error('eep_webapp_qa_vote_login', __('Please log in to vote.', 'nexdirectory'), ['status' => 403]);
            }
            $question_id = sanitize_key((string) $request->get_param('question_id'));
            if ($question_id === '') {
                return new WP_Error('eep_webapp_qa_vote_missing', __('Choose a question first.', 'nexdirectory'), ['status' => 400]);
            }
            foreach ($rows as &$row) {
                if (!is_array($row)) {
                    continue;
                }
                if (sanitize_key((string) ($row['id'] ?? '')) !== $question_id) {
                    continue;
                }
                $voters = array_values(array_unique(array_filter(array_map('absint', (array) ($row['voters'] ?? [])))));
                if (in_array($user_id, $voters, true)) {
                    $voters = array_values(array_filter($voters, static function ($entry) use ($user_id) {
                        return (int) $entry !== (int) $user_id;
                    }));
                } else {
                    $voters[] = $user_id;
                }
                $row['voters'] = array_values(array_unique(array_filter(array_map('absint', $voters))));
                break;
            }
            unset($row);
        } elseif ($action === 'answer') {
            if (!$can_manage && !($qa_allow_attendee_answers && $user_id > 0)) {
                return new WP_Error('eep_webapp_qa_answer_forbidden', __('Only event moderators can answer questions.', 'nexdirectory'), ['status' => 403]);
            }
            $question_id = sanitize_key((string) $request->get_param('question_id'));
            $answer = eep_sanitize_textarea((string) $request->get_param('answer'));
            if ($question_id === '' || trim($answer) === '') {
                return new WP_Error('eep_webapp_qa_answer_invalid', __('Provide both a question and answer.', 'nexdirectory'), ['status' => 400]);
            }
            $answer_user = $user_id > 0 ? get_user_by('id', $user_id) : null;
            $answer_user_name = $answer_user instanceof WP_User
                ? sanitize_text_field((string) ($answer_user->display_name ?: $answer_user->user_login))
                : __('Event Team', 'nexdirectory');
            foreach ($rows as &$row) {
                if (!is_array($row)) {
                    continue;
                }
                if (sanitize_key((string) ($row['id'] ?? '')) !== $question_id) {
                    continue;
                }
                $row['answer'] = $answer;
                $row['answer_user_id'] = $user_id;
                $row['answer_user_name'] = $answer_user_name;
                $row['status'] = 'open';
                $row['answered_at_gmt'] = current_time('mysql', true);
                break;
            }
            unset($row);
        } elseif ($action === 'remove') {
            if (!$can_manage) {
                return new WP_Error('eep_webapp_qa_remove_forbidden', __('Only event moderators can remove questions.', 'nexdirectory'), ['status' => 403]);
            }
            $question_id = sanitize_key((string) $request->get_param('question_id'));
            $filtered = [];
            foreach ($rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                if (sanitize_key((string) ($row['id'] ?? '')) === $question_id) {
                    continue;
                }
                $filtered[] = $row;
            }
            $rows = $filtered;
        } else {
            return new WP_Error('eep_webapp_qa_invalid_action', __('Unsupported Q&A action.', 'nexdirectory'), ['status' => 400]);
        }

        $this->update_event_webapp_store_rows($post_id, 'eep_webapp_qa_items', $rows, 400);

        return rest_ensure_response([
            'items' => $this->get_event_webapp_qa_response_rows($post_id, $user_id, $can_manage),
            'can_moderate' => $can_manage,
        ]);
    }


    public function rest_event_webapp_polls($request) {
        $context = $this->get_event_webapp_request_context($request, true);
        if (is_wp_error($context)) {
            return $context;
        }
        $post_id = absint((int) ($context['post_id'] ?? 0));
        $user_id = absint((int) ($context['user_id'] ?? 0));
        $can_manage = !empty($context['can_manage']) || !empty($context['can_moderate_chat']);
        $settings = is_array($context['settings'] ?? null) ? (array) $context['settings'] : [];
        $polls_enabled = !empty($settings['enable_polls']);
        $polls_vote_mode = sanitize_key((string) ($settings['polls_vote_mode'] ?? 'single'));
        if (!in_array($polls_vote_mode, ['single', 'multiple'], true)) {
            $polls_vote_mode = 'single';
        }

        if ($request->get_method() === 'GET') {
            return rest_ensure_response([
                'items' => $this->get_event_webapp_poll_response_rows($post_id, $user_id, $can_manage, $can_manage),
                'can_manage' => $can_manage,
                'can_vote' => $user_id > 0 && $polls_enabled,
                'vote_mode' => $polls_vote_mode,
            ]);
        }

        if (!$polls_enabled && !$can_manage) {
            return new WP_Error('eep_webapp_polls_disabled', __('Polls are disabled for this event.', 'nexdirectory'), ['status' => 403]);
        }

        $action = sanitize_key((string) $request->get_param('action'));
        if ($action === '') {
            $action = 'vote';
        }
        $rows = $this->get_event_webapp_poll_rows($post_id);

        if ($action === 'create') {
            if (!$can_manage) {
                return new WP_Error('eep_webapp_polls_create_forbidden', __('Only event moderators can create polls.', 'nexdirectory'), ['status' => 403]);
            }
            $question = eep_sanitize_textarea((string) $request->get_param('question'));
            $question = trim((string) $question);
            if ($question === '') {
                return new WP_Error('eep_webapp_polls_question_required', __('Enter a poll question first.', 'nexdirectory'), ['status' => 400]);
            }
            if (function_exists('mb_strlen')) {
                if (mb_strlen($question) > 300) {
                    $question = (string) mb_substr($question, 0, 300);
                }
            } elseif (strlen($question) > 300) {
                $question = (string) substr($question, 0, 300);
            }

            $options_param = $request->get_param('options');
            $options_input = [];
            if (is_array($options_param)) {
                $options_input = $options_param;
            } elseif (is_string($options_param)) {
                $options_input = preg_split('/\r\n|\r|\n|,/', (string) $options_param) ?: [];
            }
            $options_clean = array_values(array_unique(array_filter(array_map(static function ($entry) {
                $label = sanitize_text_field((string) $entry);
                if (function_exists('mb_strlen')) {
                    if (mb_strlen($label) > 120) {
                        $label = (string) mb_substr($label, 0, 120);
                    }
                } elseif (strlen($label) > 120) {
                    $label = (string) substr($label, 0, 120);
                }
                return trim((string) $label);
            }, $options_input))));
            if (count($options_clean) < 2) {
                return new WP_Error('eep_webapp_polls_options_required', __('Add at least two poll options.', 'nexdirectory'), ['status' => 400]);
            }

            $multiple = !empty($request->get_param('multiple')) || $polls_vote_mode === 'multiple';
            $status = sanitize_key((string) $request->get_param('status'));
            if (!in_array($status, ['open', 'closed', 'hidden'], true)) {
                $status = 'open';
            }
            $creator = $user_id > 0 ? get_user_by('id', $user_id) : null;
            $creator_name = $creator instanceof WP_User
                ? sanitize_text_field((string) ($creator->display_name ?: $creator->user_login))
                : __('Event Team', 'nexdirectory');
            $poll_id = $this->event_webapp_generate_id('poll');
            $option_rows = [];
            foreach ($options_clean as $option_index => $option_label) {
                $option_rows[] = [
                    'id' => sanitize_key($poll_id . '-opt-' . ((int) $option_index + 1)),
                    'label' => $option_label,
                    'voters' => [],
                ];
            }
            $rows[] = [
                'id' => $poll_id,
                'question' => $question,
                'status' => $status,
                'multiple' => $multiple,
                'options' => $option_rows,
                'created_by_user_id' => $user_id,
                'created_by_name' => $creator_name,
                'created_at_gmt' => current_time('mysql', true),
            ];
        } elseif ($action === 'vote') {
            if ($user_id <= 0) {
                return new WP_Error('eep_webapp_polls_vote_login', __('Please log in to vote.', 'nexdirectory'), ['status' => 403]);
            }
            $poll_id = sanitize_key((string) $request->get_param('poll_id'));
            if ($poll_id === '') {
                return new WP_Error('eep_webapp_polls_poll_required', __('Choose a poll first.', 'nexdirectory'), ['status' => 400]);
            }
            $option_ids_raw = $request->get_param('option_ids');
            $option_ids = [];
            if (is_array($option_ids_raw)) {
                $option_ids = $option_ids_raw;
            } elseif (is_string($option_ids_raw) && trim((string) $option_ids_raw) !== '') {
                $option_ids = preg_split('/[\s,]+/', (string) $option_ids_raw) ?: [];
            }
            $single_option_id = sanitize_key((string) $request->get_param('option_id'));
            if ($single_option_id !== '') {
                $option_ids[] = $single_option_id;
            }
            $option_ids = array_values(array_unique(array_filter(array_map('sanitize_key', $option_ids))));
            if (empty($option_ids)) {
                return new WP_Error('eep_webapp_polls_option_required', __('Choose at least one poll option.', 'nexdirectory'), ['status' => 400]);
            }

            $poll_found = false;
            foreach ($rows as &$row) {
                if (!is_array($row)) {
                    continue;
                }
                if (sanitize_key((string) ($row['id'] ?? '')) !== $poll_id) {
                    continue;
                }
                $poll_found = true;
                $status = sanitize_key((string) ($row['status'] ?? 'open'));
                if ($status !== 'open' && !$can_manage) {
                    return new WP_Error('eep_webapp_polls_closed', __('This poll is closed.', 'nexdirectory'), ['status' => 403]);
                }

                $options = is_array($row['options'] ?? null) ? (array) $row['options'] : [];
                $allowed_option_ids = [];
                foreach ($options as $option_row) {
                    if (!is_array($option_row)) {
                        continue;
                    }
                    $allowed_id = sanitize_key((string) ($option_row['id'] ?? ''));
                    if ($allowed_id !== '') {
                        $allowed_option_ids[] = $allowed_id;
                    }
                }
                $allowed_lookup = array_fill_keys($allowed_option_ids, true);
                $selected_option_ids = array_values(array_filter($option_ids, static function ($option_id) use ($allowed_lookup) {
                    return isset($allowed_lookup[$option_id]);
                }));
                if (empty($selected_option_ids)) {
                    return new WP_Error('eep_webapp_polls_invalid_option', __('Selected option is not valid.', 'nexdirectory'), ['status' => 400]);
                }

                $allow_multiple_for_poll = !empty($row['multiple']) || $polls_vote_mode === 'multiple';
                if (!$allow_multiple_for_poll && count($selected_option_ids) > 1) {
                    $selected_option_ids = [(string) $selected_option_ids[0]];
                }

                $selected_lookup = array_fill_keys($selected_option_ids, true);
                foreach ($options as &$option_row) {
                    if (!is_array($option_row)) {
                        continue;
                    }
                    $option_id = sanitize_key((string) ($option_row['id'] ?? ''));
                    $voters = array_values(array_unique(array_filter(array_map('absint', (array) ($option_row['voters'] ?? [])))));
                    $voters = array_values(array_filter($voters, static function ($entry_user_id) use ($user_id) {
                        return (int) $entry_user_id !== (int) $user_id;
                    }));
                    if ($option_id !== '' && isset($selected_lookup[$option_id])) {
                        $voters[] = $user_id;
                    }
                    $option_row['voters'] = array_values(array_unique(array_filter(array_map('absint', $voters))));
                }
                unset($option_row);
                $row['options'] = $options;
                break;
            }
            unset($row);
            if (!$poll_found) {
                return new WP_Error('eep_webapp_polls_not_found', __('Poll not found.', 'nexdirectory'), ['status' => 404]);
            }
        } elseif (in_array($action, ['close', 'open', 'hide', 'show', 'delete', 'clear_votes'], true)) {
            if (!$can_manage) {
                return new WP_Error('eep_webapp_polls_manage_forbidden', __('Only event moderators can manage polls.', 'nexdirectory'), ['status' => 403]);
            }
            $poll_id = sanitize_key((string) $request->get_param('poll_id'));
            if ($poll_id === '') {
                return new WP_Error('eep_webapp_polls_poll_required', __('Choose a poll first.', 'nexdirectory'), ['status' => 400]);
            }

            if ($action === 'delete') {
                $rows = array_values(array_filter($rows, static function ($row) use ($poll_id) {
                    if (!is_array($row)) {
                        return false;
                    }
                    return sanitize_key((string) ($row['id'] ?? '')) !== $poll_id;
                }));
            } else {
                foreach ($rows as &$row) {
                    if (!is_array($row)) {
                        continue;
                    }
                    if (sanitize_key((string) ($row['id'] ?? '')) !== $poll_id) {
                        continue;
                    }
                    if ($action === 'close') {
                        $row['status'] = 'closed';
                    } elseif ($action === 'open') {
                        $row['status'] = 'open';
                    } elseif ($action === 'hide') {
                        $row['status'] = 'hidden';
                    } elseif ($action === 'show') {
                        $row['status'] = 'open';
                    } elseif ($action === 'clear_votes') {
                        $options = is_array($row['options'] ?? null) ? (array) $row['options'] : [];
                        foreach ($options as &$option_row) {
                            if (!is_array($option_row)) {
                                continue;
                            }
                            $option_row['voters'] = [];
                        }
                        unset($option_row);
                        $row['options'] = $options;
                    }
                    break;
                }
                unset($row);
            }
        } else {
            return new WP_Error('eep_webapp_polls_invalid_action', __('Unsupported poll action.', 'nexdirectory'), ['status' => 400]);
        }

        $this->update_event_webapp_store_rows($post_id, 'eep_webapp_polls_feed', $rows, 240);

        return rest_ensure_response([
            'items' => $this->get_event_webapp_poll_response_rows($post_id, $user_id, $can_manage, $can_manage),
            'can_manage' => $can_manage,
            'can_vote' => $user_id > 0 && $polls_enabled,
            'vote_mode' => $polls_vote_mode,
        ]);
    }


    private function maybe_dispatch_event_webapp_push_notification($post_id, array $settings, array $notification_row, array $context = []) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return;
        }

        $push_enabled = !empty($settings['push_enabled']);
        $push_provider = sanitize_key((string) ($settings['push_provider'] ?? 'none'));
        $push_key = trim((string) ($settings['push_key'] ?? ''));
        if (!$push_enabled || $push_provider === '' || $push_provider === 'none') {
            return;
        }

        $event_title = sanitize_text_field((string) get_the_title($post_id));
        $event_permalink = esc_url_raw((string) get_permalink($post_id));
        $event_app_url = esc_url_raw((string) $this->get_event_webapp_page_url($post_id));
        $notification_text = eep_sanitize_textarea((string) ($notification_row['text'] ?? ''));
        $notification_type = sanitize_key((string) ($notification_row['type'] ?? 'info'));

        $payload = [
            'provider' => $push_provider,
            'event_id' => $post_id,
            'event_title' => $event_title,
            'event_permalink' => $event_permalink,
            'event_webapp_url' => $event_app_url,
            'notification' => [
                'id' => sanitize_key((string) ($notification_row['id'] ?? '')),
                'text' => $notification_text,
                'type' => $notification_type,
                'author_id' => absint((int) ($notification_row['author_id'] ?? 0)),
                'author_name' => sanitize_text_field((string) ($notification_row['author_name'] ?? '')),
                'created_at_gmt' => sanitize_text_field((string) ($notification_row['created_at_gmt'] ?? '')),
            ],
            'context' => [
                'trigger' => sanitize_key((string) ($context['trigger'] ?? 'notification_publish')),
                'user_id' => absint((int) ($context['user_id'] ?? 0)),
            ],
        ];

        // Built-in behavior for custom providers: treat push key as webhook endpoint.
        if ($push_provider === 'custom' && $push_key !== '' && wp_http_validate_url($push_key)) {
            wp_remote_post($push_key, [
                'timeout' => 10,
                'headers' => [
                    'Content-Type' => 'application/json; charset=utf-8',
                    'Accept' => 'application/json',
                ],
                'body' => wp_json_encode($payload),
                'data_format' => 'body',
            ]);
        }

        /**
         * Fires when an event webapp notification is published and push dispatch is enabled.
         *
         * @param string $push_provider
         * @param string $push_key
         * @param array<string, mixed> $payload
         * @param int $post_id
         * @param array<string, mixed> $settings
         * @param array<string, mixed> $notification_row
         */
        do_action('eep_event_webapp_push_notification', $push_provider, $push_key, $payload, $post_id, $settings, $notification_row);
    }


    public function rest_event_webapp_notifications($request) {
        $context = $this->get_event_webapp_request_context($request, true);
        if (is_wp_error($context)) {
            return $context;
        }
        $post_id = absint((int) ($context['post_id'] ?? 0));
        $user_id = absint((int) ($context['user_id'] ?? 0));
        $can_manage = !empty($context['can_manage']) || !empty($context['can_moderate_chat']);
        $settings = is_array($context['settings'] ?? null) ? (array) $context['settings'] : [];

        if ($request->get_method() === 'GET') {
            return rest_ensure_response([
                'items' => $this->get_event_webapp_notification_rows($post_id),
                'can_publish' => $can_manage,
            ]);
        }

        if (empty($settings['enable_notifications']) && !$can_manage) {
            return new WP_Error('eep_webapp_notifications_disabled', __('Notifications are disabled for this event.', 'nexdirectory'), ['status' => 403]);
        }
        if (!$can_manage) {
            return new WP_Error('eep_webapp_notifications_forbidden', __('Only event moderators can post notifications.', 'nexdirectory'), ['status' => 403]);
        }

        $action = sanitize_key((string) $request->get_param('action'));
        if ($action === '') {
            $action = 'publish';
        }
        $rows = $this->get_event_webapp_notification_rows($post_id);

        if ($action === 'publish') {
            $text = eep_sanitize_textarea((string) $request->get_param('text'));
            if (trim($text) === '') {
                return new WP_Error('eep_webapp_notification_empty', __('Type a notification message first.', 'nexdirectory'), ['status' => 400]);
            }
            if (function_exists('mb_strlen')) {
                if (mb_strlen($text) > 400) {
                    $text = (string) mb_substr($text, 0, 400);
                }
            } elseif (strlen($text) > 400) {
                $text = (string) substr($text, 0, 400);
            }
            $type = sanitize_key((string) $request->get_param('type'));
            if (!in_array($type, ['info', 'success', 'warning', 'alert'], true)) {
                $type = 'info';
            }
            $publisher = $user_id > 0 ? get_user_by('id', $user_id) : null;
            $publisher_name = $publisher instanceof WP_User
                ? sanitize_text_field((string) ($publisher->display_name ?: $publisher->user_login))
                : __('Event Team', 'nexdirectory');
            $notification_row = [
                'id' => $this->event_webapp_generate_id('notif'),
                'text' => $text,
                'type' => $type,
                'author_id' => $user_id,
                'author_name' => $publisher_name,
                'created_at_gmt' => current_time('mysql', true),
            ];
            $rows[] = $notification_row;
            $this->maybe_dispatch_event_webapp_push_notification($post_id, $settings, $notification_row, [
                'trigger' => 'notification_publish',
                'user_id' => $user_id,
            ]);
        } elseif ($action === 'delete') {
            $notification_id = sanitize_key((string) $request->get_param('notification_id'));
            if ($notification_id === '') {
                return new WP_Error('eep_webapp_notification_missing', __('Select a notification first.', 'nexdirectory'), ['status' => 400]);
            }
            $filtered = [];
            foreach ($rows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                if (sanitize_key((string) ($row['id'] ?? '')) === $notification_id) {
                    continue;
                }
                $filtered[] = $row;
            }
            $rows = $filtered;
        } else {
            return new WP_Error('eep_webapp_notification_invalid_action', __('Unsupported notification action.', 'nexdirectory'), ['status' => 400]);
        }

        $this->update_event_webapp_store_rows($post_id, 'eep_webapp_notifications_feed', $rows, 300);

        return rest_ensure_response([
            'items' => $this->get_event_webapp_notification_rows($post_id),
            'can_publish' => $can_manage,
        ]);
    }


    public function rest_event_webapp_itinerary($request) {
        $context = $this->get_event_webapp_request_context($request, true);
        if (is_wp_error($context)) {
            return $context;
        }
        $post_id = absint((int) ($context['post_id'] ?? 0));
        $user_id = absint((int) ($context['user_id'] ?? 0));
        $settings = is_array($context['settings'] ?? null) ? (array) $context['settings'] : [];
        $can_manage = !empty($context['can_manage']);
        $auth_identity = is_array($context['auth_identity'] ?? null) ? (array) $context['auth_identity'] : [];
        $auth_source = sanitize_key((string) ($auth_identity['source'] ?? 'guest'));
        $session_user_id = is_user_logged_in() ? absint((int) get_current_user_id()) : 0;
        $is_signed_in_session = $session_user_id > 0 && $user_id > 0 && $session_user_id === $user_id && $auth_source === 'session';

        if (!$is_signed_in_session) {
            if ($request->get_method() === 'GET') {
                return rest_ensure_response([
                    'saved_session_keys' => [],
                    'notes' => [],
                    'updated_at' => '',
                    'requires_login' => true,
                ]);
            }
            return new WP_Error('eep_webapp_itinerary_login', __('Please log in to save your custom itinerary.', 'nexdirectory'), ['status' => 403]);
        }

        if (empty($settings['enable_custom_itinerary']) && !$can_manage) {
            return new WP_Error('eep_webapp_itinerary_disabled', __('Custom itinerary is disabled for this event.', 'nexdirectory'), ['status' => 403]);
        }

        if ($request->get_method() === 'GET') {
            return rest_ensure_response($this->get_event_webapp_user_itinerary($post_id, $user_id));
        }

        $action = sanitize_key((string) $request->get_param('action'));
        if ($action === '') {
            $action = 'save';
        }
        if ($action !== 'save') {
            return new WP_Error('eep_webapp_itinerary_invalid_action', __('Unsupported itinerary action.', 'nexdirectory'), ['status' => 400]);
        }

        $allowed_session_keys = array_values(array_filter(array_map(static function ($row) {
            return sanitize_key((string) ($row['id'] ?? ''));
        }, $this->build_event_webapp_session_rows($post_id))));
        $allowed_lookup = array_fill_keys($allowed_session_keys, true);

        $saved_session_keys = $this->normalize_event_webapp_saved_session_keys($request->get_param('saved_session_keys'));
        if (!empty($allowed_lookup)) {
            $saved_session_keys = array_values(array_filter($saved_session_keys, static function ($key) use ($allowed_lookup) {
                return isset($allowed_lookup[$key]);
            }));
        }
        $notes = $this->normalize_event_webapp_notes_map($request->get_param('notes'));
        if (!empty($allowed_lookup)) {
            $notes = array_filter($notes, static function ($note_text, $session_key) use ($allowed_lookup) {
                return isset($allowed_lookup[$session_key]);
            }, ARRAY_FILTER_USE_BOTH);
        }

        $this->update_event_webapp_user_itinerary($post_id, $user_id, $saved_session_keys, $notes);

        return rest_ensure_response($this->get_event_webapp_user_itinerary($post_id, $user_id));
    }


    public function rest_user_search_permission() {
        return $this->user_can_manage_organizers();
    }


    public function rest_authenticated_permission() {
        return is_user_logged_in() && $this->user_can_access_platform();
    }


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


    public function rest_chatbot_permission() {
        if (function_exists('epw_fs') && !epw_fs()->can_use_premium_code__premium_only()) {
            return new WP_Error('eep_chatbot_premium_required', __('Event chatbot is available on premium plans only.', 'nexdirectory'), ['status' => 403]);
        }
        if ((string) get_option('eep_chatbot_enabled', '0') !== '1') {
            return new WP_Error('eep_chatbot_disabled', __('Event chatbot is disabled.', 'nexdirectory'), ['status' => 403]);
        }
        if (!$this->chatbot_has_api_configuration()) {
            return new WP_Error('eep_chatbot_not_configured', __('Event chatbot API is not configured.', 'nexdirectory'), ['status' => 503]);
        }
        return true;
    }


    public function rest_moderation_permission() {
        return $this->user_can_moderate_events();
    }


    public function rest_chatbot_query($request) {
        $query = eep_sanitize_textarea((string) $request->get_param('query'));
        if (trim($query) === '') {
            return new WP_Error('eep_chatbot_empty_query', __('Please enter a question.', 'nexdirectory'), ['status' => 400]);
        }
        if (function_exists('mb_strlen')) {
            if (mb_strlen($query) > 500) {
                $query = (string) mb_substr($query, 0, 500);
            }
        } elseif (strlen($query) > 500) {
            $query = (string) substr($query, 0, 500);
        }

        $rate_limit = $this->chatbot_check_rate_limit();
        if (is_wp_error($rate_limit)) {
            return $rate_limit;
        }

        $history_raw = $request->get_param('history');
        $history = [];
        if (is_array($history_raw)) {
            $history_raw = array_slice($history_raw, -10);
            foreach ($history_raw as $history_row) {
                if (!is_array($history_row)) {
                    continue;
                }
                $role = sanitize_key((string) ($history_row['role'] ?? ''));
                if (!in_array($role, ['user', 'assistant'], true)) {
                    continue;
                }
                $content = eep_sanitize_textarea((string) ($history_row['content'] ?? ''));
                $content = trim($content);
                if ($content === '') {
                    continue;
                }
                if (function_exists('mb_strlen')) {
                    if (mb_strlen($content) > 700) {
                        $content = (string) mb_substr($content, 0, 700);
                    }
                } elseif (strlen($content) > 700) {
                    $content = (string) substr($content, 0, 700);
                }
                $history[] = [
                    'role' => $role,
                    'content' => $content,
                ];
            }
        }

        $preferences_raw = $request->get_param('preferences');
        $preferences = [];
        if (is_array($preferences_raw)) {
            $mode = sanitize_key((string) ($preferences_raw['mode'] ?? ''));
            $price = sanitize_key((string) ($preferences_raw['price'] ?? ''));
            $date = sanitize_key((string) ($preferences_raw['date'] ?? ''));
            $location = sanitize_text_field((string) ($preferences_raw['location'] ?? ''));
            $near_me = !empty($preferences_raw['near_me']) ? '1' : '0';
            if (in_array($mode, ['online', 'in_person'], true)) {
                $preferences['mode'] = $mode;
            }
            if (in_array($price, ['free', 'paid'], true)) {
                $preferences['price'] = $price;
            }
            if (in_array($date, ['this_week', 'next_14_days', 'later_this_year'], true)) {
                $preferences['date'] = $date;
            }
            if ($location !== '') {
                $preferences['location'] = $location;
            }
            if ($near_me === '1') {
                $preferences['near_me'] = '1';
            }
        }

        $answer_payload = $this->chatbot_answer_query($query, $history, $preferences);
        if (is_wp_error($answer_payload)) {
            return $answer_payload;
        }

        return rest_ensure_response($answer_payload);
    }


    private function validate_extension_request_auth($request, $require_signature = false) {
        if ((string) get_option('eep_extension_enabled', '0') !== '1') {
            return new WP_Error('eep_extension_disabled', __('Extension import API is disabled in settings.', 'nexdirectory'), ['status' => 403]);
        }

        $app_pass = (string) $this->get_extension_app_pass(false);
        if ($app_pass === '') {
            return new WP_Error('eep_extension_not_configured', __('Extension App Pass is not configured.', 'nexdirectory'), ['status' => 500]);
        }

        $provided_pass = trim((string) $request->get_header('x-eep-app-pass'));
        if ($provided_pass === '' || !hash_equals($app_pass, $provided_pass)) {
            $this->append_runtime_log('warning', 'Extension auth failed: invalid app pass', [
                'route' => (string) $request->get_route(),
                'ip' => sanitize_text_field((string) ($_SERVER['REMOTE_ADDR'] ?? '')),
            ]);
            return new WP_Error('eep_extension_unauthorized', __('Invalid extension credentials.', 'nexdirectory'), ['status' => 401]);
        }

        $timestamp_raw = trim((string) $request->get_header('x-eep-timestamp'));
        $signature = trim((string) $request->get_header('x-eep-signature'));
        if ($require_signature || $timestamp_raw !== '' || $signature !== '') {
            if ($timestamp_raw === '' || $signature === '') {
                return new WP_Error('eep_extension_signature_missing', __('Signed timestamp headers are required for this request.', 'nexdirectory'), ['status' => 401]);
            }
            if (!preg_match('/^-?\d+$/', $timestamp_raw)) {
                return new WP_Error('eep_extension_signature_invalid', __('Invalid extension signature timestamp.', 'nexdirectory'), ['status' => 401]);
            }
            $timestamp = (int) $timestamp_raw;
            if (abs(time() - $timestamp) > 300) {
                return new WP_Error('eep_extension_signature_expired', __('Extension signature expired. Retry the request.', 'nexdirectory'), ['status' => 401]);
            }
            $body = (string) $request->get_body();
            $expected_signature = hash_hmac('sha256', $timestamp_raw . '.' . $body, $app_pass);
            if (!hash_equals($expected_signature, $signature)) {
                return new WP_Error('eep_extension_signature_invalid', __('Extension request signature check failed.', 'nexdirectory'), ['status' => 401]);
            }
        }

        return true;
    }


    public function rest_extension_permission($request) {
        $route = (string) $request->get_route();
        $require_signature = strpos($route, '/extension/import') !== false
            || strpos($route, '/extension/preferences') !== false;
        return $this->validate_extension_request_auth($request, $require_signature);
    }


    private function normalize_extension_terms_value($value) {
        if (is_array($value)) {
            $parts = array_map('eep_sanitize_text', array_map('strval', $value));
            $parts = array_values(array_filter(array_map('trim', $parts), static function ($item) {
                return $item !== '';
            }));
            return implode(',', $parts);
        }
        return sanitize_text_field((string) $value);
    }


    private function normalize_extension_url_lines($value) {
        if (is_array($value)) {
            $value = implode("\n", array_map('strval', $value));
        }
        $urls = $this->parse_multiline_urls((string) $value);
        return implode("\n", array_values(array_unique(array_filter($urls))));
    }


    private function normalize_extension_sessions_json($value) {
        if (is_string($value)) {
            $decoded = json_decode($value, true);
            if (is_array($decoded)) {
                return wp_json_encode($decoded);
            }
            return wp_json_encode($this->parse_sessions($value));
        }
        if (is_array($value)) {
            return wp_json_encode($value);
        }
        return wp_json_encode([]);
    }

    private function normalize_extension_key_speakers_json($value) {
        if (is_string($value)) {
            $decoded = json_decode($value, true);
            if (is_array($decoded)) {
                return wp_json_encode($decoded);
            }
            return wp_json_encode($this->parse_key_speakers($value));
        }
        if (is_array($value)) {
            return wp_json_encode($value);
        }
        return wp_json_encode([]);
    }

    private function get_extension_custom_field_schema() {
        if ((string) get_option('eep_enable_custom_submission_fields', '0') !== '1') {
            return [];
        }

        $definitions = [];
        if (is_callable([$this, 'get_custom_form_fields'])) {
            $definitions = (array) $this->get_custom_form_fields();
        }
        if (empty($definitions)) {
            $raw = get_option('eep_custom_form_fields', '[]');
            $decoded = is_string($raw) ? json_decode($raw, true) : (is_array($raw) ? $raw : []);
            $definitions = is_array($decoded) ? $decoded : [];
        }

        $schema = [];
        $seen = [];
        foreach (array_values((array) $definitions) as $index => $definition_raw) {
            $definition = is_array($definition_raw) ? $definition_raw : [];
            if (is_callable([$this, 'sanitize_custom_field_definition'])) {
                $definition = (array) $this->sanitize_custom_field_definition($definition, (int) $index);
            }
            $id = sanitize_key((string) ($definition['id'] ?? ''));
            if ($id === '' || isset($seen[$id])) {
                continue;
            }
            $seen[$id] = true;
            $schema[] = [
                'id' => $id,
                'label' => sanitize_text_field((string) ($definition['label'] ?? $id)),
                'type' => sanitize_key((string) ($definition['type'] ?? 'text')),
                'required' => (string) ($definition['required'] ?? '0') === '1' ? '1' : '0',
                'placeholder' => sanitize_text_field((string) ($definition['placeholder'] ?? '')),
                'options' => eep_sanitize_textarea((string) ($definition['options'] ?? '')),
            ];
        }

        return $schema;
    }

    private function normalize_extension_form_field_type($type) {
        $type = sanitize_key((string) $type);
        if (in_array($type, ['text', 'textarea', 'url', 'email', 'number', 'date', 'datetime-local', 'checkbox', 'select'], true)) {
            return $type;
        }
        if ($type === 'datetime') {
            return 'datetime-local';
        }
        return 'text';
    }

    private function get_extension_core_meta_keys() {
        return [
            'eep_start',
            'eep_end',
            'eep_timezone',
            'eep_venue',
            'eep_address',
            'eep_city',
            'eep_country',
            'eep_capacity',
            'eep_price',
            'eep_ticket_sales_enabled',
            'eep_ticket_price',
            'eep_ticket_button_label',
            'eep_meta_title',
            'eep_meta_description',
            'eep_external_url',
            'eep_event_stream_url',
            'eep_contact_name',
            'eep_contact_email',
            'eep_contact_phone',
            'eep_cover_image_url',
            'eep_video_urls',
            'eep_gallery_urls',
            'eep_supporting_docs',
            'eep_official_event',
            'eep_recurrence',
            'eep_recurrence_until',
        ];
    }

    private function get_extension_submit_template_field_names() {
        static $cached = null;
        if (is_array($cached)) {
            return $cached;
        }

        $template_path = plugin_dir_path(EEP_PLUGIN_FILE) . 'templates/frontend/submit-event.php';
        if (!is_readable($template_path)) {
            $cached = [];
            return $cached;
        }

        $contents = file_get_contents($template_path);
        if (!is_string($contents) || $contents === '') {
            $cached = [];
            return $cached;
        }

        if (!preg_match_all('/name=(["\'])([^"\']+)\\1/', $contents, $matches)) {
            $cached = [];
            return $cached;
        }

        $names = [];
        foreach ((array) ($matches[2] ?? []) as $raw_name) {
            $raw_name = trim((string) $raw_name);
            if ($raw_name === '') {
                continue;
            }
            if (strpos($raw_name, '<?php') !== false) {
                if (strpos($raw_name, 'eep_tax_') === 0) {
                    $names[] = 'eep_tax_*';
                }
                continue;
            }
            $normalized = preg_replace('/\[\]$/', '', $raw_name);
            $key = sanitize_key((string) $normalized);
            if ($key === '') {
                continue;
            }
            if (strpos($key, 'eep_') !== 0 && !in_array($key, ['post_title', 'post_excerpt', 'post_content', 'event_tags'], true)) {
                continue;
            }
            $names[] = $key;
        }

        $names = array_values(array_unique($names));
        sort($names, SORT_STRING);
        $cached = $names;
        return $cached;
    }

    private function is_extension_system_submit_field($key) {
        $key = sanitize_key((string) $key);
        if ($key === '') {
            return true;
        }
        $ignored = [
            'action',
            'eep_nonce',
            'eep_website',
            'eep_recaptcha_token',
            'eep_autosave_post_id',
            'eep_edit_post_id',
            'eep_custom_fields_builder_json',
            'eep_cover_file',
            'eep_gallery_files',
            'eep_intent',
            'eep_start_date',
            'eep_start_time',
            'eep_end_date',
            'eep_end_time',
            'eep_is_recurring',
            'eep_sessions',
            'eep_youtube_video_id_1',
            'eep_youtube_video_id_2',
        ];
        return in_array($key, $ignored, true);
    }

    private function guess_extension_form_field_type_from_key($key) {
        $key = sanitize_key((string) $key);
        if ($key === '') {
            return 'text';
        }
        if (substr($key, -5) === '_date') {
            return 'date';
        }
        if (substr($key, -5) === '_time') {
            return 'text';
        }
        if (strpos($key, '_email') !== false) {
            return 'email';
        }
        if (strpos($key, '_url') !== false || strpos($key, '_website') !== false) {
            return 'url';
        }
        if (strpos($key, '_description') !== false || strpos($key, '_details') !== false || strpos($key, '_notes') !== false) {
            return 'textarea';
        }
        if (strpos($key, '_count') !== false || strpos($key, '_quantity') !== false || strpos($key, '_capacity') !== false || strpos($key, '_year') !== false) {
            return 'number';
        }
        return 'text';
    }

    private function get_extension_form_field_label($key) {
        $key = sanitize_key((string) $key);
        $label = str_replace('eep_', '', $key);
        $label = str_replace('_', ' ', (string) $label);
        $label = ucwords(trim((string) $label));
        return $label !== '' ? $label : $key;
    }

    private function get_extension_form_field_options($key) {
        $key = sanitize_key((string) $key);
        $maps = [
            'eep_job_work_mode' => ['on-site', 'hybrid', 'remote'],
            'eep_job_compensation_type' => ['Salary', 'Hourly', 'Contract / Day Rate', 'Commission', 'Volunteer'],
            'eep_job_remote_policy' => ['On-site', 'Remote', 'Hybrid'],
            'eep_job_visa_sponsorship' => ['0', '1'],
            'eep_market_price_type' => ['fixed', 'negotiable', 'auction', 'free'],
            'eep_market_seller_type' => ['private', 'business'],
            'eep_market_listing_status' => ['active', 'reserved', 'sold', 'expired'],
            'eep_market_delivery_methods' => ['pickup', 'shipping', 'local-delivery', 'digital'],
            'eep_market_returns_accepted' => ['0', '1'],
            'eep_market_accept_offers' => ['0', '1'],
            'eep_directory_listing_status' => ['active', 'temporarily-closed', 'permanently-closed'],
            'eep_directory_business_type' => ['independent-business', 'agency', 'private-owner', 'nonprofit', 'multi-location', 'franchise'],
            'eep_event_mode_option' => ['in-person', 'video-stream'],
            'eep_price_simple' => ['free', 'paid'],
            'eep_ticket_sales_enabled' => ['0', '1'],
            'eep_official_event' => ['0', '1'],
            'eep_lock_pin' => ['0', '1'],
        ];
        return isset($maps[$key]) ? implode("\n", (array) $maps[$key]) : '';
    }

    private function get_extension_form_field_schema() {
        $meta_fields = is_callable([$this, 'get_event_meta_fields']) ? (array) $this->get_event_meta_fields() : [];
        $exclude_core = $this->get_extension_core_meta_keys();
        $exclude_map = array_fill_keys($exclude_core, true);
        $schema = [];
        $seen = [];

        foreach ($meta_fields as $meta_key => $meta_type) {
            $meta_key = sanitize_key((string) $meta_key);
            if ($meta_key === '' || isset($exclude_map[$meta_key]) || isset($seen[$meta_key])) {
                continue;
            }
            $seen[$meta_key] = true;
            $options = $this->get_extension_form_field_options($meta_key);
            $normalized_type = $this->normalize_extension_form_field_type((string) $meta_type);
            if ($options !== '' && !in_array($normalized_type, ['textarea', 'checkbox'], true)) {
                $normalized_type = 'select';
            }
            if (in_array($meta_key, ['eep_market_returns_accepted', 'eep_market_accept_offers', 'eep_lock_pin', 'eep_job_visa_sponsorship'], true)) {
                $normalized_type = 'checkbox';
            }
            $schema[] = [
                'key' => $meta_key,
                'label' => $this->get_extension_form_field_label($meta_key),
                'type' => $normalized_type,
                'required' => '0',
                'placeholder' => '',
                'options' => $options,
                'source' => 'meta',
            ];
        }

        $custom_taxonomies = is_callable([$this, 'get_custom_taxonomy_definitions_for_submit'])
            ? (array) $this->get_custom_taxonomy_definitions_for_submit()
            : [];
        foreach ($custom_taxonomies as $taxonomy_row) {
            $taxonomy_row = is_array($taxonomy_row) ? $taxonomy_row : [];
            $taxonomy_slug = sanitize_key((string) ($taxonomy_row['slug'] ?? ''));
            if ($taxonomy_slug === '' || !taxonomy_exists($taxonomy_slug)) {
                continue;
            }
            $field_key = 'eep_tax_' . $taxonomy_slug;
            if (isset($seen[$field_key])) {
                continue;
            }
            $seen[$field_key] = true;
            $term_rows = get_terms([
                'taxonomy' => $taxonomy_slug,
                'hide_empty' => false,
                'orderby' => 'name',
                'order' => 'ASC',
                'fields' => 'names',
            ]);
            $option_rows = [];
            if (is_array($term_rows)) {
                foreach ($term_rows as $term_name) {
                    $clean = sanitize_text_field((string) $term_name);
                    if ($clean !== '') {
                        $option_rows[] = $clean;
                    }
                }
            }
            $schema[] = [
                'key' => $field_key,
                'label' => sanitize_text_field((string) ($taxonomy_row['label'] ?? $taxonomy_slug)),
                'type' => 'textarea',
                'required' => '0',
                'placeholder' => __('Comma separated values', 'nexdirectory'),
                'options' => implode("\n", $option_rows),
                'source' => 'taxonomy',
            ];
        }

        $submit_fields = $this->get_extension_submit_template_field_names();
        foreach ($submit_fields as $submit_key) {
            $submit_key = sanitize_key((string) $submit_key);
            if ($submit_key === '' || $submit_key === 'eep_tax_*') {
                continue;
            }
            if (isset($seen[$submit_key]) || isset($exclude_map[$submit_key]) || $this->is_extension_system_submit_field($submit_key)) {
                continue;
            }
            if (strpos($submit_key, 'eep_') !== 0) {
                continue;
            }
            $seen[$submit_key] = true;
            $options = $this->get_extension_form_field_options($submit_key);
            $normalized_type = $this->guess_extension_form_field_type_from_key($submit_key);
            if ($options !== '' && !in_array($normalized_type, ['textarea', 'checkbox'], true)) {
                $normalized_type = 'select';
            }
            if (in_array($submit_key, ['eep_market_returns_accepted', 'eep_market_accept_offers', 'eep_lock_pin', 'eep_job_visa_sponsorship'], true)) {
                $normalized_type = 'checkbox';
            }
            $schema[] = [
                'key' => $submit_key,
                'label' => $this->get_extension_form_field_label($submit_key),
                'type' => $normalized_type,
                'required' => '0',
                'placeholder' => '',
                'options' => $options,
                'source' => 'submit-template',
            ];
        }

        usort($schema, static function ($a, $b) {
            $label_a = sanitize_text_field((string) ($a['label'] ?? ''));
            $label_b = sanitize_text_field((string) ($b['label'] ?? ''));
            return strcasecmp($label_a, $label_b);
        });

        return $schema;
    }

    private function normalize_extension_custom_fields_payload(array $payload) {
        $raw = $payload['custom_fields'] ?? ($payload['custom_field_values'] ?? ($payload['eep_custom_fields_data'] ?? []));
        if (is_string($raw)) {
            $decoded = json_decode($raw, true);
            $raw = is_array($decoded) ? $decoded : [];
        }
        if (!is_array($raw)) {
            return [];
        }

        $normalized = [];
        foreach ($raw as $key => $value) {
            $id = sanitize_key((string) $key);
            if ($id === '') {
                continue;
            }
            if (is_array($value)) {
                $normalized[$id] = wp_json_encode($value);
            } elseif (is_bool($value)) {
                $normalized[$id] = $value ? '1' : '0';
            } else {
                $normalized[$id] = (string) $value;
            }
        }
        return $normalized;
    }

    private function normalize_extension_extra_fields_payload(array $payload) {
        $meta_fields = is_callable([$this, 'get_event_meta_fields']) ? (array) $this->get_event_meta_fields() : [];
        $allowed_meta_keys = array_fill_keys(array_map('sanitize_key', array_keys($meta_fields)), true);
        foreach ($this->get_extension_submit_template_field_names() as $submit_field_key) {
            $submit_field_key = sanitize_key((string) $submit_field_key);
            if ($submit_field_key === '' || strpos($submit_field_key, 'eep_') !== 0 || $this->is_extension_system_submit_field($submit_field_key)) {
                continue;
            }
            $allowed_meta_keys[$submit_field_key] = true;
        }
        $normalized = [];
        $sources = [];

        $raw_extra = $payload['extra_fields'] ?? [];
        if (is_string($raw_extra)) {
            $decoded_extra = json_decode($raw_extra, true);
            $raw_extra = is_array($decoded_extra) ? $decoded_extra : [];
        }
        if (is_array($raw_extra)) {
            $sources[] = $raw_extra;
        }
        $sources[] = $payload;

        foreach ($sources as $source) {
            if (!is_array($source)) {
                continue;
            }
            foreach ($source as $raw_key => $raw_value) {
                $key = sanitize_key((string) $raw_key);
                if ($key === '') {
                    continue;
                }
                $is_meta_key = strpos($key, 'eep_') === 0 && isset($allowed_meta_keys[$key]);
                $is_custom_tax_key = strpos($key, 'eep_tax_') === 0;
                if (!$is_meta_key && !$is_custom_tax_key) {
                    continue;
                }

                if (is_array($raw_value)) {
                    if ($is_custom_tax_key || $key === 'eep_market_delivery_methods') {
                        $parts = array_values(array_filter(array_map('sanitize_text_field', $raw_value), static function ($part) {
                            return trim((string) $part) !== '';
                        }));
                        $normalized[$key] = implode(',', $parts);
                    } else {
                        $normalized[$key] = wp_json_encode($raw_value);
                    }
                } elseif (is_bool($raw_value)) {
                    $normalized[$key] = $raw_value ? '1' : '0';
                } else {
                    $normalized[$key] = (string) $raw_value;
                }
            }
        }

        return $normalized;
    }

    private function apply_extension_import_enhanced_fields($post_id, array $row, array $payload) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return;
        }

        $seo_enabled = (string) get_option('eep_enable_organizer_event_seo', '1') === '1';
        if ($seo_enabled) {
            $meta_title = $this->sanitize_event_seo_title((string) ($row['meta_title'] ?? ($payload['meta_title'] ?? '')));
            $meta_description = $this->sanitize_event_seo_description((string) ($row['meta_description'] ?? ($payload['meta_description'] ?? '')));
            if ($meta_title !== '') {
                update_post_meta($post_id, 'eep_meta_title', $meta_title);
            }
            if ($meta_description !== '') {
                update_post_meta($post_id, 'eep_meta_description', $meta_description);
            }
        }

        $schema = $this->get_extension_custom_field_schema();
        if (empty($schema)) {
            return;
        }
        $raw_values = $this->normalize_extension_custom_fields_payload($payload);
        if (empty($raw_values)) {
            return;
        }

        if (is_callable([$this, 'sanitize_custom_form_fields_value_map'])) {
            $sanitized_values = (array) $this->sanitize_custom_form_fields_value_map($raw_values, $schema);
        } else {
            $sanitized_values = [];
            foreach ($schema as $field) {
                $field = is_array($field) ? $field : [];
                $id = sanitize_key((string) ($field['id'] ?? ''));
                if ($id === '' || !array_key_exists($id, $raw_values)) {
                    continue;
                }
                $sanitized_values[$id] = sanitize_text_field((string) $raw_values[$id]);
            }
        }

        update_post_meta($post_id, 'eep_custom_fields_schema', wp_json_encode($schema));
        if (!empty($sanitized_values)) {
            update_post_meta($post_id, 'eep_custom_fields_data', wp_json_encode($sanitized_values));
        }
    }

    private function normalize_extension_import_content_mode($value = '') {
        $mode = sanitize_key((string) $value);
        if (in_array($mode, ['rewrite', 'verbatim'], true)) {
            return $mode;
        }
        $mode = sanitize_key((string) get_option('eep_extension_import_content_mode', 'rewrite'));
        return in_array($mode, ['rewrite', 'verbatim'], true) ? $mode : 'rewrite';
    }

    private function maybe_rewrite_extension_import_copy(array $row, array $source_payload = []) {
        $description = trim((string) ($row['description'] ?? ''));
        $excerpt = trim((string) ($row['excerpt'] ?? ''));
        if ($description === '' && $excerpt === '') {
            return $row;
        }

        if (!method_exists($this, 'chatbot_http_post_json') || !method_exists($this, 'chatbot_extract_output_text')) {
            return $row;
        }
        $api_key = trim((string) get_option('eep_chatbot_openai_api_key', ''));
        if ($api_key === '') {
            return $row;
        }

        $model = method_exists($this, 'get_chatbot_text_model')
            ? (string) $this->get_chatbot_text_model()
            : 'gpt-5.2';
        if ($model === '') {
            $model = 'gpt-5.2';
        }

        $user_payload = [
            'title' => sanitize_text_field((string) ($row['title'] ?? '')),
            'source_excerpt' => eep_sanitize_textarea((string) ($source_payload['excerpt'] ?? $excerpt)),
            'source_description' => eep_strip_all_tags((string) ($source_payload['description'] ?? $description)),
            'event_url' => esc_url_raw((string) ($row['external_url'] ?? '')),
            'venue' => sanitize_text_field((string) ($row['venue'] ?? '')),
            'address' => sanitize_text_field((string) ($row['address'] ?? '')),
            'city' => sanitize_text_field((string) ($row['city'] ?? '')),
            'country' => sanitize_text_field((string) ($row['country'] ?? '')),
            'start' => sanitize_text_field((string) ($row['start'] ?? '')),
            'end' => sanitize_text_field((string) ($row['end'] ?? '')),
        ];

        $payload = [
            'model' => $model,
            'instructions' => implode("\n", [
                'Rewrite listing content for clarity, originality, and SEO while preserving factual accuracy.',
                'Do not invent names, dates, times, prices, addresses, or claims.',
                'Keep tone professional and concise.',
                'Return valid JSON only with keys: excerpt, description.',
            ]),
            'input' => [
                [
                    'role' => 'user',
                    'content' => wp_json_encode($user_payload),
                ],
            ],
            'max_output_tokens' => 900,
            'text' => [
                'format' => [
                    'type' => 'json_object',
                ],
            ],
        ];

        $response = $this->chatbot_http_post_json('https://api.openai.com/v1/responses', $payload, 40);
        if (is_wp_error($response)) {
            $this->append_runtime_log('warning', 'Extension import rewrite failed', [
                'reason' => $response->get_error_message(),
            ]);
            return $row;
        }

        $rewritten = [];
        if (method_exists($this, 'chatbot_extract_json_object_from_text')) {
            $text = (string) $this->chatbot_extract_output_text($response);
            $decoded = $this->chatbot_extract_json_object_from_text($text);
            if (is_array($decoded)) {
                $rewritten = $decoded;
            }
        }
        if (empty($rewritten) && isset($response['output_text'])) {
            $decoded = json_decode((string) $response['output_text'], true);
            if (is_array($decoded)) {
                $rewritten = $decoded;
            }
        }
        if (empty($rewritten)) {
            return $row;
        }

        $new_excerpt = eep_sanitize_textarea((string) ($rewritten['excerpt'] ?? ''));
        $new_description = wp_kses_post((string) ($rewritten['description'] ?? ''));

        if ($new_description !== '') {
            $row['description'] = $new_description;
        }
        if ($new_excerpt !== '') {
            $row['excerpt'] = $new_excerpt;
        } elseif ((string) ($row['excerpt'] ?? '') === '' && (string) ($row['description'] ?? '') !== '') {
            $row['excerpt'] = wp_trim_words(eep_strip_all_tags((string) $row['description']), 26);
        }

        return $row;
    }


    private function build_extension_import_row(array $payload, array $extension_preferences = []) {
        $default_status = sanitize_key((string) get_option('eep_extension_import_status', 'draft'));
        if (!in_array($default_status, ['draft', 'publish'], true)) {
            $default_status = 'draft';
        }
        $media_policy = sanitize_key((string) get_option('eep_extension_import_media_policy', ''));
        if (!in_array($media_policy, ['none', 'images', 'images_video', 'video'], true)) {
            $media_policy = 'none';
        }
        $pref_status = sanitize_key((string) ($extension_preferences['import_status'] ?? ''));
        if (in_array($pref_status, ['draft', 'publish'], true)) {
            $default_status = $pref_status;
        }
        $pref_media_policy = sanitize_key((string) ($extension_preferences['media_upload_policy'] ?? ''));
        if (in_array($pref_media_policy, ['none', 'images', 'images_video', 'video'], true)) {
            $media_policy = $pref_media_policy;
        }
        $content_mode = $this->normalize_extension_import_content_mode($extension_preferences['content_mode'] ?? '');

        $start = (string) ($payload['start'] ?? $payload['start_date'] ?? ($payload['eep_start'] ?? ''));
        $end = (string) ($payload['end'] ?? $payload['end_date'] ?? ($payload['eep_end'] ?? ''));
        $event_url = esc_url_raw((string) ($payload['external_url'] ?? $payload['event_url'] ?? $payload['url'] ?? ($payload['eep_external_url'] ?? '')));
        $stream_url = esc_url_raw((string) ($payload['stream_url'] ?? $payload['video_stream_url'] ?? ($payload['eep_event_stream_url'] ?? '')));
        $video_urls = $this->normalize_extension_url_lines($payload['video_urls'] ?? ($payload['videos'] ?? ($payload['eep_video_urls'] ?? '')));
        if ($stream_url !== '') {
            $video_urls = $this->normalize_extension_url_lines($video_urls . "\n" . $stream_url);
        }
        $recurrence = sanitize_key((string) ($payload['recurrence'] ?? ($payload['eep_recurrence'] ?? 'none')));
        if (!in_array($recurrence, ['none', 'daily', 'weekly', 'monthly'], true)) {
            $recurrence = 'none';
        }
        $custom_fields_payload = $this->normalize_extension_custom_fields_payload($payload);
        $extra_fields_payload = $this->normalize_extension_extra_fields_payload($payload);

        $row = [
            'title' => sanitize_text_field((string) ($payload['title'] ?? '')),
            'description' => wp_kses_post((string) ($payload['description'] ?? $payload['content'] ?? '')),
            'excerpt' => eep_sanitize_textarea((string) ($payload['excerpt'] ?? '')),
            'meta_title' => sanitize_text_field((string) ($payload['meta_title'] ?? ($payload['eep_meta_title'] ?? ''))),
            'meta_description' => eep_sanitize_textarea((string) ($payload['meta_description'] ?? ($payload['eep_meta_description'] ?? ''))),
            'start' => $this->normalize_datetime_local($start),
            'end' => $this->normalize_datetime_local($end),
            'timezone' => sanitize_text_field((string) ($payload['timezone'] ?? ($payload['eep_timezone'] ?? ''))),
            'venue' => sanitize_text_field((string) ($payload['venue'] ?? $payload['location_name'] ?? ($payload['eep_venue'] ?? ''))),
            'address' => sanitize_text_field((string) ($payload['address'] ?? $payload['location'] ?? ($payload['eep_address'] ?? ''))),
            'city' => sanitize_text_field((string) ($payload['city'] ?? ($payload['eep_city'] ?? ''))),
            'country' => sanitize_text_field((string) ($payload['country'] ?? ($payload['eep_country'] ?? ''))),
            'lat' => is_numeric($payload['lat'] ?? ($payload['eep_lat'] ?? null)) ? (string) ($payload['lat'] ?? ($payload['eep_lat'] ?? '')) : '',
            'lng' => is_numeric($payload['lng'] ?? ($payload['eep_lng'] ?? null)) ? (string) ($payload['lng'] ?? ($payload['eep_lng'] ?? '')) : '',
            'capacity' => absint((int) ($payload['capacity'] ?? ($payload['eep_capacity'] ?? 0))),
            'price' => sanitize_text_field((string) ($payload['price'] ?? ($payload['eep_price'] ?? ''))),
            'ticket_sales_enabled' => in_array((string) ($payload['ticket_sales_enabled'] ?? ($payload['eep_ticket_sales_enabled'] ?? '0')), ['1', 'true', 'yes'], true) ? '1' : '0',
            'ticket_price' => sanitize_text_field((string) ($payload['ticket_price'] ?? ($payload['eep_ticket_price'] ?? ''))),
            'ticket_button_label' => sanitize_text_field((string) ($payload['ticket_button_label'] ?? ($payload['eep_ticket_button_label'] ?? ''))),
            'external_url' => $event_url,
            'stream_url' => $stream_url,
            'contact_email' => sanitize_email((string) ($payload['contact_email'] ?? ($payload['eep_contact_email'] ?? ''))),
            'contact_phone' => sanitize_text_field((string) ($payload['contact_phone'] ?? ($payload['eep_contact_phone'] ?? ''))),
            'contact_name' => sanitize_text_field((string) ($payload['contact_name'] ?? ($payload['eep_contact_name'] ?? ''))),
            'cover_image_url' => esc_url_raw((string) ($payload['cover_image_url'] ?? $payload['image_url'] ?? $payload['thumbnail_url'] ?? ($payload['eep_cover_image_url'] ?? ''))),
            'official_event' => $this->normalize_official_event_value($payload['official_event'] ?? ($payload['official'] ?? ($payload['eep_official_event'] ?? '0'))),
            'video_urls' => $video_urls,
            'gallery_urls' => $this->normalize_extension_url_lines($payload['gallery_urls'] ?? ($payload['gallery'] ?? $payload['images'] ?? ($payload['eep_gallery_urls'] ?? ''))),
            'supporting_docs_urls' => $this->normalize_extension_url_lines($payload['supporting_docs_urls'] ?? ($payload['supporting_docs'] ?? ($payload['documents'] ?? ($payload['eep_supporting_docs'] ?? '')))),
            'sessions' => $this->normalize_extension_sessions_json($payload['sessions'] ?? ($payload['itinerary'] ?? ($payload['agenda'] ?? []))),
            'key_speakers' => $this->normalize_extension_key_speakers_json($payload['key_speakers'] ?? ($payload['speakers'] ?? ($payload['speaker_profiles'] ?? []))),
            'event_type' => $this->normalize_extension_terms_value($payload['event_type'] ?? ($payload['type'] ?? '')),
            'event_mode' => $this->normalize_extension_terms_value($payload['event_mode'] ?? ($payload['mode'] ?? '')),
            'event_price_range' => $this->normalize_extension_terms_value($payload['event_price_range'] ?? ($payload['price_range'] ?? '')),
            'event_tags' => $this->normalize_extension_terms_value($payload['event_tags'] ?? ($payload['tags'] ?? '')),
            'recurrence' => $recurrence,
            'recurrence_until' => $this->normalize_datetime_local((string) ($payload['recurrence_until'] ?? ($payload['repeat_until'] ?? ($payload['eep_recurrence_until'] ?? '')))),
            'custom_fields' => $custom_fields_payload,
            'status' => $default_status,
        ];

        foreach ($extra_fields_payload as $extra_key => $extra_value) {
            if (!array_key_exists($extra_key, $row) || trim((string) $row[$extra_key]) === '') {
                $row[$extra_key] = $extra_value;
            }
        }
        if (!empty($extra_fields_payload)) {
            $row['extra_fields'] = $extra_fields_payload;
        }

        if ($row['excerpt'] === '' && $row['description'] !== '') {
            $row['excerpt'] = wp_trim_words(eep_strip_all_tags($row['description']), 26);
        }

        if ($content_mode === 'rewrite') {
            $row = $this->maybe_rewrite_extension_import_copy($row, $payload);
        }

        if ($row['city'] === '' && $row['address'] !== '') {
            $parts = array_filter(array_map('trim', explode(',', $row['address'])));
            if (!empty($parts)) {
                $row['city'] = (string) end($parts);
            }
        }
        $parsed_extension_tags = $this->parse_event_tags_input((string) ($row['event_tags'] ?? ''), 8, 40);
        if (is_wp_error($parsed_extension_tags)) {
            $row['event_tags'] = '';
        } else {
            $row['event_tags'] = implode(',', (array) $parsed_extension_tags);
        }

        if ($media_policy === 'none') {
            $row['cover_image_url'] = '';
            $row['gallery_urls'] = '';
            $row['video_urls'] = '';
        } elseif ($media_policy === 'images') {
            $row['video_urls'] = '';
        } elseif ($media_policy === 'video') {
            $row['cover_image_url'] = '';
            $row['gallery_urls'] = '';
        }

        return $row;
    }


    public function rest_extension_ping($request) {
        $import_status = sanitize_key((string) get_option('eep_extension_import_status', 'draft'));
        if (!in_array($import_status, ['draft', 'publish'], true)) {
            $import_status = 'draft';
        }
        $media_policy = sanitize_key((string) get_option('eep_extension_import_media_policy', ''));
        if (!in_array($media_policy, ['none', 'images', 'images_video', 'video'], true)) {
            $media_policy = 'none';
        }
        $custom_fields = $this->get_extension_custom_field_schema();
        $form_fields = $this->get_extension_form_field_schema();
        $submit_fields = $this->get_extension_submit_template_field_names();
        $core_meta_keys = $this->get_extension_core_meta_keys();
        return rest_ensure_response([
            'ok' => true,
            'plugin' => $this->get_white_label_brand_name(),
            'version' => self::VERSION,
            'site_url' => home_url('/'),
            'rest_base' => esc_url_raw(rest_url('eep/v1/')),
            'defaults' => [
                'import_status' => $import_status,
                'mark_claimable' => (string) get_option('eep_extension_import_mark_claimable', '1') === '1',
                'media_upload_policy' => $media_policy,
                'content_mode' => $this->normalize_extension_import_content_mode(),
                'notify_potential_owner_default' => (string) get_option('eep_extension_notify_potential_owner_default', '0') === '1',
                'allow_images' => in_array($media_policy, ['images', 'images_video'], true),
                'show_autogenerated_disclaimer' => (string) get_option('eep_enable_autogenerated_disclaimer', '1') === '1',
            ],
            'features' => [
                'claim_listings' => (string) get_option('eep_enable_claim_listings', '0') === '1',
                'duplicate_detection' => (string) get_option('eep_enable_duplicate_detection', '1') === '1',
                'custom_fields_enabled' => !empty($custom_fields),
                'form_fields_enabled' => !empty($form_fields),
            ],
            'required_fields' => ['title', 'start'],
            'supports' => ['gallery_urls', 'video_urls', 'supporting_docs_urls', 'sessions', 'key_speakers', 'event_tags', 'event_type', 'event_mode', 'event_price_range', 'recurrence', 'recurrence_until', 'meta_title', 'meta_description', 'ticket_sales_enabled', 'ticket_price', 'ticket_button_label', 'custom_fields', 'form_fields', 'extra_fields', 'custom_taxonomy_terms'],
            'custom_fields' => $custom_fields,
            'form_fields' => $form_fields,
            'submit_fields' => $submit_fields,
            'extension_core_meta_keys' => $core_meta_keys,
            'timestamp' => time(),
        ]);
    }


    public function rest_extension_update_preferences($request) {
        $payload = $request->get_json_params();
        if (!is_array($payload)) {
            $decoded = json_decode((string) $request->get_body(), true);
            $payload = is_array($decoded) ? $decoded : [];
        }

        $import_status = sanitize_key((string) ($payload['import_status'] ?? ''));
        if (!in_array($import_status, ['draft', 'publish'], true)) {
            $import_status = sanitize_key((string) get_option('eep_extension_import_status', 'draft'));
            if (!in_array($import_status, ['draft', 'publish'], true)) {
                $import_status = 'draft';
            }
        }

        $media_policy = sanitize_key((string) ($payload['media_upload_policy'] ?? ''));
        if (!in_array($media_policy, ['none', 'images', 'video', 'images_video'], true)) {
            $media_policy = sanitize_key((string) get_option('eep_extension_import_media_policy', 'none'));
            if (!in_array($media_policy, ['none', 'images', 'video', 'images_video'], true)) {
                $media_policy = 'none';
            }
        }

        $notify_default = (string) ($payload['notify_potential_owner_default'] ?? '');
        $notify_default = in_array($notify_default, ['1', 'true', 'yes'], true) ? '1' : '0';
        $content_mode = $this->normalize_extension_import_content_mode($payload['content_mode'] ?? '');

        update_option('eep_extension_import_status', $import_status, false);
        update_option('eep_extension_import_media_policy', $media_policy, false);
        update_option('eep_extension_notify_potential_owner_default', $notify_default, false);
        update_option('eep_extension_import_content_mode', $content_mode, false);

        return rest_ensure_response([
            'ok' => true,
            'message' => __('Extension preferences updated.', 'nexdirectory'),
            'defaults' => [
                'import_status' => $import_status,
                'media_upload_policy' => $media_policy,
                'content_mode' => $content_mode,
                'notify_potential_owner_default' => $notify_default === '1',
            ],
        ]);
    }


    public function rest_extension_import_event($request) {
        $raw_payload = $request->get_json_params();
        if (!is_array($raw_payload)) {
            $decoded = json_decode((string) $request->get_body(), true);
            $raw_payload = is_array($decoded) ? $decoded : [];
        }
        $payload = $raw_payload;
        if (isset($raw_payload['event']) && is_array($raw_payload['event'])) {
            $payload = (array) $raw_payload['event'];
        }
        if (empty($payload)) {
            return new WP_Error('eep_extension_empty_payload', __('Extension payload is empty.', 'nexdirectory'), ['status' => 400]);
        }

        $extension_preferences = [];
        if (isset($raw_payload['extension_preferences']) && is_array($raw_payload['extension_preferences'])) {
            $extension_preferences = (array) $raw_payload['extension_preferences'];
        }

        $row = $this->build_extension_import_row($payload, $extension_preferences);
        if (trim((string) ($row['title'] ?? '')) === '') {
            return new WP_Error('eep_extension_missing_title', __('Missing required event title.', 'nexdirectory'), ['status' => 400]);
        }
        if (trim((string) ($row['start'] ?? '')) === '') {
            return new WP_Error('eep_extension_missing_start', __('Missing required start date/time.', 'nexdirectory'), ['status' => 400]);
        }

        $duplicate_detection_enabled = (string) get_option('eep_enable_duplicate_detection', '1') === '1';
        $duplicate_event_id = 0;
        if ($duplicate_detection_enabled) {
            $duplicate_event_id = $this->find_duplicate_event_candidate(
                (string) ($row['title'] ?? ''),
                (string) ($row['start'] ?? ''),
                (string) ($row['city'] ?? ''),
                (string) ($row['address'] ?? ''),
                (string) ($row['venue'] ?? ''),
                0
            );
        }

        $author_id = $this->get_default_import_author_id(absint((int) ($payload['author_id'] ?? 0)));
        $created = $this->import_event_from_row(
            $row,
            (string) ($row['status'] ?? 'draft'),
            $duplicate_detection_enabled,
            $author_id
        );
        if (is_wp_error($created)) {
            if ($created->get_error_code() === 'duplicate_event') {
                $duplicate_data = $created->get_error_data('duplicate_event');
                $duplicate_post_id = 0;
                if (is_array($duplicate_data)) {
                    $duplicate_post_id = absint((int) ($duplicate_data['duplicate_post_id'] ?? 0));
                }
                if ($duplicate_post_id <= 0) {
                    $duplicate_post_id = $duplicate_event_id;
                }
                $this->append_runtime_log('info', 'Extension event skipped as duplicate', [
                    'duplicate_post_id' => (string) $duplicate_post_id,
                    'source_url' => esc_url_raw((string) ($raw_payload['source_url'] ?? ($payload['source_url'] ?? ''))),
                ]);

                return rest_ensure_response([
                    'ok' => true,
                    'skipped_duplicate' => true,
                    'post_id' => $duplicate_post_id,
                    'status' => $duplicate_post_id > 0 ? (string) get_post_status($duplicate_post_id) : '',
                    'permalink' => $duplicate_post_id > 0 ? (string) get_permalink($duplicate_post_id) : '',
                    'edit_url' => $duplicate_post_id > 0 ? (string) $this->get_frontend_event_edit_url($duplicate_post_id) : '',
                    'duplicate_candidate' => $duplicate_post_id,
                    'message' => __('Duplicate detected. Existing listing was kept and import was skipped.', 'nexdirectory'),
                ]);
            }
            return new WP_Error(
                (string) $created->get_error_code(),
                (string) $created->get_error_message(),
                ['status' => 400]
            );
        }

        $post_id = absint((int) $created);
        if ($post_id <= 0) {
            return new WP_Error('eep_extension_import_failed', __('Event import failed unexpectedly.', 'nexdirectory'), ['status' => 500]);
        }

        update_post_meta($post_id, 'eep_import_source', 'chrome_extension');
        update_post_meta($post_id, 'eep_generated_by_plugin', '1');
        update_post_meta($post_id, 'eep_autogenerated_event', '1');
        $source_url = esc_url_raw((string) ($raw_payload['source_url'] ?? ($payload['source_url'] ?? '')));
        update_post_meta($post_id, 'eep_import_source_url', $source_url);
        update_post_meta($post_id, 'eep_import_payload_hash', hash('sha256', wp_json_encode($raw_payload)));
        $mark_claimable = (string) get_option('eep_extension_import_mark_claimable', '1') === '1';
        update_post_meta($post_id, 'eep_claimable', $mark_claimable ? '1' : '0');
        $this->apply_extension_import_enhanced_fields($post_id, $row, $payload);

        $post_status = (string) get_post_status($post_id);
        if ($post_status === 'pending') {
            $this->notify_admin_submission($post_id);
            $this->notify_author_status(
                $post_id,
                'pending',
                sprintf(
                    __('Submitted via %s Chrome extension.', 'nexdirectory'),
                    $this->get_white_label_brand_name()
                )
            );
        }

        $claim_page_url = $this->get_platform_page_url('eep_claim_listing_page_url', 'claim-listing', home_url('/claim-listing/'));
        $claim_url = $mark_claimable ? eep_add_query_arg('event_id', $post_id, $claim_page_url) : '';
        $owner_notification_sent = false;
        $owner_notification_email = '';
        if ($this->should_send_extension_owner_notification($extension_preferences)) {
            $owner_notification_email = sanitize_email((string) ($extension_preferences['notify_potential_owner_email'] ?? ''));
            if (!is_email($owner_notification_email)) {
                $owner_notification_email = sanitize_email((string) ($row['contact_email'] ?? ''));
            }
            $owner_notification_sent = $this->send_extension_owner_notification(
                $post_id,
                $owner_notification_email,
                $claim_url,
                $source_url
            );
        }
        $this->append_runtime_log('info', 'Extension event imported', [
            'post_id' => (string) $post_id,
            'status' => $post_status,
            'author_id' => (string) $author_id,
            'duplicate_candidate' => (string) $duplicate_event_id,
            'owner_notification_sent' => $owner_notification_sent ? '1' : '0',
            'owner_notification_email' => (string) $owner_notification_email,
        ]);

        return rest_ensure_response([
            'ok' => true,
            'post_id' => $post_id,
            'status' => $post_status,
            'permalink' => get_permalink($post_id),
            'edit_url' => $this->get_frontend_event_edit_url($post_id),
            'claimable' => $mark_claimable,
            'claim_url' => $claim_url,
            'owner_notification_sent' => $owner_notification_sent,
            'owner_notification_email' => $owner_notification_email,
            'duplicate_candidate' => $duplicate_event_id,
            'message' => __('Event imported successfully.', 'nexdirectory'),
        ]);
    }


    private function should_send_extension_owner_notification(array $extension_preferences = []) {
        if (array_key_exists('notify_potential_owner_default', $extension_preferences)) {
            $value = $extension_preferences['notify_potential_owner_default'];
            if (is_bool($value)) {
                return $value;
            }
            $normalized = strtolower(trim((string) $value));
            return in_array($normalized, ['1', 'true', 'yes', 'on'], true);
        }

        // Fallback behavior when extension did not explicitly provide a per-import preference:
        // respect admin-level toggle + default preference.
        if ((string) get_option('eep_notify_extension_potential_owner', '0') !== '1') {
            return false;
        }
        return (string) get_option('eep_extension_notify_potential_owner_default', '0') === '1';
    }


    private function send_extension_owner_notification($post_id, $recipient_email, $claim_url = '', $source_url = '') {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return false;
        }

        $recipient_email = sanitize_email((string) $recipient_email);
        if (!is_email($recipient_email)) {
            return false;
        }

        $source_url = esc_url_raw((string) $source_url);
        $claim_url = esc_url_raw((string) $claim_url);
        $listing_url = (string) get_permalink($post_id);
        $site_name = sanitize_text_field((string) $this->get_white_label_brand_name());

        $subject_template = (string) get_option(
            'eep_email_subject_extension_potential_owner',
            $this->get_default_event_email_template_value('eep_email_subject_extension_potential_owner')
        );
        $body_template = (string) get_option(
            'eep_email_body_extension_potential_owner',
            $this->get_default_event_email_template_value('eep_email_body_extension_potential_owner')
        );

        $extra_tokens = [
            '{claim_url}' => $claim_url,
            '{source_url}' => $source_url,
            '{site_name}' => $site_name,
            '{listing_url}' => $listing_url,
        ];

        $subject = trim((string) $this->render_event_email_subject($subject_template, $post_id, '', $extra_tokens));
        if ($subject === '') {
            $subject = sprintf(
                __('Your listing is live on %s (claim available)', 'nexdirectory'),
                $site_name
            );
        }
        $custom_body_html = $this->render_event_email_body($body_template, $post_id, '', '', $extra_tokens);
        $fallback_text = sprintf(
            "A listing matching your event appears on %s.\n\nTitle: %s\nView listing: %s\nClaim listing: %s\nSource page: %s",
            $site_name,
            get_the_title($post_id),
            $listing_url,
            $claim_url,
            $source_url
        );

        $sent = (bool) $this->send_platform_email($recipient_email, $subject, '', [
            'post_id' => $post_id,
            'event_title' => get_the_title($post_id),
            'event_url' => $listing_url,
            'custom_body_html' => $custom_body_html,
            'fallback_text' => $fallback_text,
        ]);
        $this->append_runtime_log($sent ? 'info' : 'warning', 'Extension owner notification email ' . ($sent ? 'sent' : 'failed'), [
            'post_id' => (string) $post_id,
            'recipient' => (string) $recipient_email,
            'transport' => (string) get_option('eep_smtp_enabled', '0') === '1' ? 'smtp' : 'wp_mail',
        ]);
        return $sent;
    }


    public function rest_search_users($request) {
        if (!$this->user_can_manage_organizers()) {
            return new WP_Error('eep_forbidden', __('Not allowed.', 'nexdirectory'), ['status' => 403]);
        }

        $search = sanitize_text_field((string) $request->get_param('q'));
        $limit = absint((int) $request->get_param('limit'));
        if ($limit < 1) {
            $limit = 100;
        }
        if ($limit > 200) {
            $limit = 200;
        }

        $users = get_users([
            'search' => $search !== '' ? '*' . $search . '*' : '',
            'search_columns' => ['user_login', 'user_email', 'display_name'],
            'number' => $limit,
            'orderby' => 'registered',
            'order' => 'DESC',
        ]);
        $ui_labels = $this->get_site_profile_ui_labels();

        return rest_ensure_response([
            'count' => is_array($users) ? count($users) : 0,
            'html' => $this->render_user_management_rows($users, $ui_labels),
        ]);
    }


    public function rest_submit_event($request) {
        if (!is_user_logged_in() || !$this->user_can_submit_events()) {
            return new WP_Error('eep_forbidden', __('You must be logged in.', 'nexdirectory'), ['status' => 403]);
        }
        return new WP_Error(
            'eep_submit_not_available',
            __('REST submit is not enabled for this action yet. Please submit using the form button.', 'nexdirectory'),
            ['status' => 501]
        );
    }


    public function rest_save_filters($request) {
        if (!is_user_logged_in()) {
            return new WP_Error('eep_forbidden', __('You must be logged in.', 'nexdirectory'), ['status' => 403]);
        }

        $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')),
        ];
        if (!in_array((string) ($filters['official'] ?? ''), ['0', '1'], true)) {
            $filters['official'] = '';
        }
        update_user_meta(get_current_user_id(), 'eep_saved_filters', $filters);

        return rest_ensure_response([
            'saved' => $filters,
            'message' => __('Filters saved to your account.', 'nexdirectory'),
        ]);
    }


    public function rest_inbox_poll($request) {
        if (!is_user_logged_in()) {
            return new WP_Error('eep_forbidden', __('You must be logged in.', 'nexdirectory'), ['status' => 403]);
        }

        $user_id = (int) get_current_user_id();
        $last_seen_message_id = absint((int) $request->get_param('last_seen_message_id'));
        $latest = $this->get_inbox_latest_unread_for_user($user_id);
        $unread_count = $this->get_inbox_unread_count_for_user($user_id);

        $latest_message_id = absint((int) ($latest['id'] ?? 0));
        $thread_id = absint((int) ($latest['thread_id'] ?? 0));
        $post_id = absint((int) ($latest['post_id'] ?? 0));
        $sender_id = absint((int) ($latest['sender_id'] ?? 0));
        $sender_name = __('Member', 'nexdirectory');
        if ($sender_id > 0) {
            $sender = get_user_by('id', $sender_id);
            if ($sender instanceof WP_User) {
                $sender_name = sanitize_text_field((string) ($sender->display_name ?: $sender->user_login));
            }
        }
        $event_title = $post_id > 0 ? sanitize_text_field((string) get_the_title($post_id)) : '';
        $subject = sanitize_text_field((string) ($latest['subject'] ?? ''));
        if ($subject === '' && $event_title !== '') {
            $subject = sprintf(__('Message about %s', 'nexdirectory'), $event_title);
        }

        $has_new = $latest_message_id > 0 && $latest_message_id > $last_seen_message_id;
        $toast_message = $event_title !== ''
            ? sprintf(__('New message about %1$s from %2$s.', 'nexdirectory'), $event_title, $sender_name)
            : sprintf(__('New message from %s.', 'nexdirectory'), $sender_name);

        return rest_ensure_response([
            'ok' => true,
            'unread_count' => (int) $unread_count,
            'latest_message_id' => $latest_message_id,
            'thread_id' => $thread_id,
            'post_id' => $post_id,
            'subject' => $subject,
            'sender_name' => $sender_name,
            'event_title' => $event_title,
            'thread_url' => $thread_id > 0 ? $this->build_inbox_thread_url($thread_id, $latest_message_id) : $this->get_notifications_page_url(),
            'has_new' => $has_new,
            'toast_message' => $toast_message,
        ]);
    }


    public function rest_rsvp_event($request) {
        if (!is_user_logged_in()) {
            return new WP_Error('eep_forbidden', __('Please log in to RSVP.', 'nexdirectory'), ['status' => 403]);
        }

        $post_id = absint((int) $request->get_param('post_id'));
        $intent = sanitize_key((string) $request->get_param('intent'));
        if ($intent === '') {
            $intent = 'join';
        }

        $result = $this->process_rsvp_action($post_id, $intent, get_current_user_id());
        if (is_wp_error($result)) {
            return new WP_Error(
                (string) $result->get_error_code(),
                (string) $result->get_error_message(),
                ['status' => 400]
            );
        }

        return rest_ensure_response($result);
    }


    public function rest_moderate_event($request) {
        if (!$this->user_can_moderate_events()) {
            return new WP_Error('eep_forbidden', __('Not allowed.', 'nexdirectory'), ['status' => 403]);
        }

        $post_id = absint((int) ($request->get_param('post_id') ?: $request->get_param('event_id')));
        $decision_raw = $request->get_param('decision');
        if ($decision_raw === null || $decision_raw === '') {
            $decision_raw = $request->get_param('moderation_action');
        }
        if (is_array($decision_raw)) {
            $decision_raw = end($decision_raw);
        }
        $decision = sanitize_key((string) $decision_raw);
        $note = eep_sanitize_textarea((string) $request->get_param('moderator_note'));
        if ($post_id <= 0 || !in_array($decision, ['approve', 'reject', 'request_changes'], true)) {
            $this->append_runtime_log('warning', 'Invalid moderation request via REST', [
                'post_id' => (string) $post_id,
                'decision' => (string) $decision,
                'user_id' => (string) get_current_user_id(),
            ]);
            return new WP_Error('eep_bad_request', __('Invalid moderation request.', 'nexdirectory'), ['status' => 400]);
        }

        $result = $this->apply_moderation_decision($post_id, $decision, $note);
        if (is_wp_error($result)) {
            return new WP_Error(
                (string) $result->get_error_code(),
                (string) $result->get_error_message(),
                ['status' => 400]
            );
        }

        return rest_ensure_response([
            'post_id' => $post_id,
            'decision' => $decision,
            'message' => (string) $result,
            'new_status' => (string) get_post_status($post_id),
            'pending_count' => (int) $this->get_pending_events_count(),
        ]);
    }


    private function parse_rest_post_ids($value) {
        if (is_string($value)) {
            $chunks = array_filter(array_map('trim', explode(',', $value)));
            $value = $chunks;
        }
        if (!is_array($value)) {
            return [];
        }
        $ids = array_map('absint', $value);
        $ids = array_values(array_unique(array_filter($ids)));
        return $ids;
    }


    public function rest_moderate_events_bulk($request) {
        if (!$this->user_can_moderate_events()) {
            return new WP_Error('eep_forbidden', __('Not allowed.', 'nexdirectory'), ['status' => 403]);
        }

        $decision_raw = $request->get_param('decision');
        if ($decision_raw === null || $decision_raw === '') {
            $decision_raw = $request->get_param('bulk_decision');
        }
        if (is_array($decision_raw)) {
            $decision_raw = end($decision_raw);
        }
        $decision = sanitize_key((string) $decision_raw);
        $note = eep_sanitize_textarea((string) $request->get_param('moderator_note'));
        $post_ids = $this->parse_rest_post_ids($request->get_param('post_ids'));
        if (empty($post_ids) || !in_array($decision, ['approve', 'reject', 'request_changes'], true)) {
            $this->append_runtime_log('warning', 'Invalid bulk moderation request via REST', [
                'decision' => (string) $decision,
                'post_ids' => (string) wp_json_encode($post_ids),
                'user_id' => (string) get_current_user_id(),
            ]);
            return new WP_Error('eep_bad_request', __('Select events and a valid action for bulk moderation.', 'nexdirectory'), ['status' => 400]);
        }

        $success = 0;
        $failed = 0;
        $errors = [];
        foreach ($post_ids as $post_id) {
            $result = $this->apply_moderation_decision((int) $post_id, $decision, $note);
            if (is_wp_error($result)) {
                $failed++;
                if (count($errors) < 10) {
                    $errors[] = [
                        'post_id' => (int) $post_id,
                        'message' => (string) $result->get_error_message(),
                    ];
                }
            } else {
                $success++;
            }
        }

        return rest_ensure_response([
            'decision' => $decision,
            'success' => $success,
            'failed' => $failed,
            'errors' => $errors,
            'message' => sprintf(
                __('Bulk moderation complete. Success: %1$d · Failed: %2$d.', 'nexdirectory'),
                (int) $success,
                (int) $failed
            ),
            'pending_count' => (int) $this->get_pending_events_count(),
        ]);
    }


    public function handle_manage_user_role() {
        $is_ajax_request = (string) wp_unslash($_POST['eep_ajax'] ?? '') === '1';
        $redirect_to = $this->get_safe_redirect_url(wp_unslash($_POST['redirect_to'] ?? ''), wp_get_referer() ?: home_url('/'));
        $respond_error = function ($message, $status_code = 400) use ($is_ajax_request, $redirect_to) {
            if ($is_ajax_request) {
                wp_send_json_error(['message' => (string) $message], (int) $status_code);
            }
            $this->redirect_with_message($redirect_to, (string) $message, 'error');
        };
        $respond_success = function ($message, array $payload = []) use ($is_ajax_request, $redirect_to) {
            if ($is_ajax_request) {
                wp_send_json_success(array_merge(['message' => (string) $message], $payload));
            }
            $this->redirect_with_message($redirect_to, (string) $message);
        };

        if (!$this->user_can_manage_organizers()) {
            $respond_error(__('Not allowed.', 'nexdirectory'), 403);
        }

        $user_id = isset($_POST['user_id']) ? absint($_POST['user_id']) : 0;
        $new_role = isset($_POST['role']) ? sanitize_key(wp_unslash($_POST['role'])) : '';
        if (!$user_id || !in_array($new_role, ['subscriber', 'event_submitter'], true)) {
            $respond_error(__('Invalid request.', 'nexdirectory'), 400);
        }

        if (!isset($_POST['eep_manage_user_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_manage_user_nonce'])), 'eep_manage_user_role_' . $user_id)) {
            $respond_error(__('Invalid request.', 'nexdirectory'), 400);
        }

        $user = get_user_by('id', $user_id);
        if (!$user instanceof WP_User || in_array('administrator', (array) $user->roles, true)) {
            $respond_error(__('Invalid target user.', 'nexdirectory'), 400);
        }

        $user->set_role($new_role);
        $respond_success(
            __('User role updated.', 'nexdirectory'),
            [
                'user_id' => (int) $user_id,
                'role' => (string) $new_role,
                'role_label' => $new_role === 'event_submitter'
                    ? __('Event Submitter', 'nexdirectory')
                    : __('Subscriber', 'nexdirectory'),
            ]
        );
    }


    public function handle_toggle_verified() {
        $is_ajax_request = (string) wp_unslash($_POST['eep_ajax'] ?? '') === '1';
        $redirect_to = $this->get_safe_redirect_url(wp_unslash($_POST['redirect_to'] ?? ''), wp_get_referer() ?: home_url('/'));
        $respond_error = function ($message, $status_code = 400) use ($is_ajax_request, $redirect_to) {
            if ($is_ajax_request) {
                wp_send_json_error(['message' => (string) $message], (int) $status_code);
            }
            $this->redirect_with_message($redirect_to, (string) $message, 'error');
        };
        $respond_success = function ($message, array $payload = []) use ($is_ajax_request, $redirect_to) {
            if ($is_ajax_request) {
                wp_send_json_success(array_merge(['message' => (string) $message], $payload));
            }
            $this->redirect_with_message($redirect_to, (string) $message);
        };

        if (!$this->user_can_manage_organizers()) {
            $respond_error(__('Not allowed.', 'nexdirectory'), 403);
        }
        $user_id = isset($_POST['user_id']) ? absint($_POST['user_id']) : 0;
        if (!$user_id) {
            $respond_error(__('Invalid user.', 'nexdirectory'), 400);
        }
        if (!isset($_POST['eep_toggle_verified_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_toggle_verified_nonce'])), 'eep_toggle_verified_' . $user_id)) {
            $respond_error(__('Invalid request.', 'nexdirectory'), 400);
        }

        $requested_verified = sanitize_key((string) wp_unslash($_POST['verified'] ?? ''));
        $current = get_user_meta($user_id, 'eep_verified_organizer', true) === '1';
        $target_verified = $current;
        if ($requested_verified !== '') {
            $target_verified = in_array($requested_verified, ['1', 'true', 'yes'], true);
        } else {
            $target_verified = !$current;
        }
        update_user_meta($user_id, 'eep_verified_organizer', $target_verified ? '1' : '0');

        $respond_success(
            $target_verified ? __('Organizer verified.', 'nexdirectory') : __('Organizer badge removed.', 'nexdirectory'),
            [
                'user_id' => (int) $user_id,
                'verified' => (bool) $target_verified,
            ]
        );
    }


    public function handle_toggle_user_edit_trust() {
        $is_ajax_request = (string) wp_unslash($_POST['eep_ajax'] ?? '') === '1';
        $redirect_to = $this->get_safe_redirect_url(wp_unslash($_POST['redirect_to'] ?? ''), wp_get_referer() ?: home_url('/'));
        $respond_error = function ($message, $status_code = 400) use ($is_ajax_request, $redirect_to) {
            if ($is_ajax_request) {
                wp_send_json_error(['message' => (string) $message], (int) $status_code);
            }
            $this->redirect_with_message($redirect_to, (string) $message, 'error');
        };
        $respond_success = function ($message, array $payload = []) use ($is_ajax_request, $redirect_to) {
            if ($is_ajax_request) {
                wp_send_json_success(array_merge(['message' => (string) $message], $payload));
            }
            $this->redirect_with_message($redirect_to, (string) $message);
        };

        if (!$this->user_can_manage_organizers()) {
            $respond_error(__('Not allowed.', 'nexdirectory'), 403);
        }
        $user_id = isset($_POST['user_id']) ? absint($_POST['user_id']) : 0;
        if (!$user_id) {
            $respond_error(__('Invalid user.', 'nexdirectory'), 400);
        }
        if (!isset($_POST['eep_toggle_user_edit_trust_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_toggle_user_edit_trust_nonce'])), 'eep_toggle_user_edit_trust_' . $user_id)) {
            $respond_error(__('Invalid request.', 'nexdirectory'), 400);
        }

        $user = get_user_by('id', $user_id);
        if (!$user instanceof WP_User || in_array('administrator', (array) $user->roles, true)) {
            $respond_error(__('Invalid target user.', 'nexdirectory'), 400);
        }

        $is_trusted = (string) get_user_meta($user_id, 'eep_trusted_editor', true) === '1';
        $requested_state = sanitize_key((string) wp_unslash($_POST['trusted'] ?? ''));
        if (in_array($requested_state, ['trusted', 'require_approval'], true)) {
            $target_trusted = $requested_state === 'trusted';
        } else {
            $target_trusted = !$is_trusted;
        }

        update_user_meta($user_id, 'eep_trusted_editor', $target_trusted ? '1' : '0');

        $respond_success(
            $target_trusted
                ? __('Trusted edit bypass enabled. This user can update published listings without admin re-approval.', 'nexdirectory')
                : __('Trusted edit bypass removed. This user now follows the global edit approval policy.', 'nexdirectory'),
            [
                'user_id' => (int) $user_id,
                'trusted' => $target_trusted ? 'trusted' : 'require_approval',
            ]
        );
    }


    public function handle_manage_user_note() {
        $is_ajax_request = (string) wp_unslash($_POST['eep_ajax'] ?? '') === '1';
        $redirect_to = $this->get_safe_redirect_url(wp_unslash($_POST['redirect_to'] ?? ''), wp_get_referer() ?: home_url('/'));
        $respond_error = function ($message, $status_code = 400) use ($is_ajax_request, $redirect_to) {
            if ($is_ajax_request) {
                wp_send_json_error(['message' => (string) $message], (int) $status_code);
            }
            $this->redirect_with_message($redirect_to, (string) $message, 'error');
        };
        $respond_success = function ($message, array $payload = []) use ($is_ajax_request, $redirect_to) {
            if ($is_ajax_request) {
                wp_send_json_success(array_merge(['message' => (string) $message], $payload));
            }
            $this->redirect_with_message($redirect_to, (string) $message);
        };

        if (!$this->user_can_manage_organizers()) {
            $respond_error(__('Not allowed.', 'nexdirectory'), 403);
        }

        $user_id = isset($_POST['user_id']) ? absint($_POST['user_id']) : 0;
        if ($user_id <= 0) {
            $respond_error(__('Invalid user.', 'nexdirectory'), 400);
        }
        if (!isset($_POST['eep_manage_user_note_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_manage_user_note_nonce'])), 'eep_manage_user_note_' . $user_id)) {
            $respond_error(__('Invalid request.', 'nexdirectory'), 400);
        }

        $user = get_user_by('id', $user_id);
        if (!$user instanceof WP_User) {
            $respond_error(__('Invalid target user.', 'nexdirectory'), 400);
        }

        $admin_note = eep_sanitize_textarea((string) wp_unslash($_POST['admin_note'] ?? ''));
        if ($admin_note === '') {
            delete_user_meta($user_id, 'eep_admin_user_note');
            $message = __('Admin note removed.', 'nexdirectory');
        } else {
            update_user_meta($user_id, 'eep_admin_user_note', $admin_note);
            $message = __('Admin note saved.', 'nexdirectory');
        }

        $respond_success(
            $message,
            [
                'user_id' => (int) $user_id,
                'admin_note' => (string) $admin_note,
            ]
        );
    }


    public function handle_manage_user_event() {
        $is_ajax_request = (string) wp_unslash($_POST['eep_ajax'] ?? '') === '1';
        $redirect = $this->get_safe_redirect_url(wp_unslash($_POST['redirect_to'] ?? ''), wp_get_referer() ?: home_url('/'));
        $respond_error = function ($message, $status_code = 400) use ($is_ajax_request, $redirect) {
            if ($is_ajax_request) {
                wp_send_json_error(['message' => (string) $message], (int) $status_code);
            }
            $this->redirect_with_message($redirect, (string) $message, 'error');
        };
        $respond_success = function ($message, array $payload = []) use ($is_ajax_request, $redirect) {
            if ($is_ajax_request) {
                wp_send_json_success(array_merge(['message' => (string) $message], $payload));
            }
            $this->redirect_with_message($redirect, (string) $message);
        };

        if (!$this->user_can_moderate_events()) {
            $respond_error(__('Not allowed.', 'nexdirectory'), 403);
        }
        $ui_labels = $this->get_site_profile_ui_labels();
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $singular_lower = sanitize_text_field((string) ($ui_labels['singular_lower'] ?? strtolower((string) $singular_label)));

        $user_id = isset($_POST['user_id']) ? absint($_POST['user_id']) : 0;
        $post_id = isset($_POST['event_id']) ? absint($_POST['event_id']) : 0;
        $intent = sanitize_key((string) wp_unslash($_POST['intent'] ?? ''));
        if (!$user_id || !$post_id || !in_array($intent, ['open_event', 'publish_event', 'unpublish_event', 'email_owner'], true)) {
            $respond_error(sprintf(__('Invalid %s management request.', 'nexdirectory'), $singular_lower), 400);
        }
        if (!isset($_POST['eep_manage_user_event_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_manage_user_event_nonce'])), 'eep_manage_user_event_' . $user_id)) {
            $respond_error(__('Invalid request.', 'nexdirectory'), 400);
        }
        if (get_post_type($post_id) !== self::POST_TYPE) {
            $respond_error(sprintf(__('Selected post is not a valid %s.', 'nexdirectory'), $singular_lower), 400);
        }
        $author_id = (int) get_post_field('post_author', $post_id);
        if ($author_id !== $user_id) {
            $respond_error(sprintf(__('Selected %s does not belong to the selected user.', 'nexdirectory'), $singular_lower), 400);
        }

        if ($intent === 'open_event') {
            $open_url = (string) get_permalink($post_id);
            if ((string) get_post_status($post_id) !== 'publish') {
                $preview_url = get_preview_post_link($post_id);
                if (is_string($preview_url) && $preview_url !== '') {
                    $open_url = $preview_url;
                }
            }
            if ($is_ajax_request) {
                $respond_success(
                    sprintf(__('%s link ready.', 'nexdirectory'), $singular_label),
                    [
                        'post_id' => (int) $post_id,
                        'intent' => 'open_event',
                        'open_url' => (string) $open_url,
                    ]
                );
            }
            $this->safe_redirect_or_fallback($open_url);
        }

        if ($intent === 'publish_event') {
            $result = wp_update_post([
                'ID' => $post_id,
                'post_status' => 'publish',
            ]);
            if (is_wp_error($result)) {
                $respond_error($result->get_error_message(), 500);
            }
            $respond_success(
                sprintf(__('%s published.', 'nexdirectory'), $singular_label),
                [
                    'post_id' => (int) $post_id,
                    'intent' => 'publish_event',
                    'new_status' => 'publish',
                    'status_label' => ucfirst('publish'),
                ]
            );
        }

        if ($intent === 'unpublish_event') {
            $result = wp_update_post([
                'ID' => $post_id,
                'post_status' => 'draft',
            ]);
            if (is_wp_error($result)) {
                $respond_error($result->get_error_message(), 500);
            }
            $this->notify_author_status($post_id, 'draft', sprintf(__('%s unpublished by administrator.', 'nexdirectory'), $singular_label));
            $respond_success(
                sprintf(__('%s moved to draft.', 'nexdirectory'), $singular_label),
                [
                    'post_id' => (int) $post_id,
                    'intent' => 'unpublish_event',
                    'new_status' => 'draft',
                    'status_label' => ucfirst('draft'),
                ]
            );
        }

        $owner = get_user_by('id', $user_id);
        if (!$owner || !is_email($owner->user_email)) {
            $respond_error(sprintf(__('%s owner email is invalid.', 'nexdirectory'), $singular_label), 400);
        }
        $subject = sanitize_text_field((string) wp_unslash($_POST['admin_subject'] ?? ''));
        if ($subject === '') {
            $subject = sprintf(__('Admin message about your %1$s: %2$s', 'nexdirectory'), $singular_lower, get_the_title($post_id));
        }
        $admin_note = eep_sanitize_textarea((string) wp_unslash($_POST['admin_message'] ?? ''));
        if ($admin_note === '') {
            $admin_note = sprintf(__('Your %s has an admin update. Please review your listing details.', 'nexdirectory'), $singular_lower);
        }
        $admin_user = wp_get_current_user();
        $headers = [];
        if ($admin_user instanceof WP_User && is_email($admin_user->user_email)) {
            $headers[] = 'Reply-To: ' . $admin_user->display_name . ' <' . $admin_user->user_email . '>';
        }
        $fallback_text = sprintf(
            "Hello %s,\n\n%s\n\n%s: %s\nLink: %s",
            $owner->display_name ?: $owner->user_login,
            $admin_note,
            $singular_label,
            get_the_title($post_id),
            get_permalink($post_id)
        );
        $sent = $this->send_platform_email($owner->user_email, $subject, '', [
            'fallback_text' => $fallback_text,
        ], $headers);
        if ($sent) {
            $respond_success(
                sprintf(__('Message emailed to %s owner.', 'nexdirectory'), $singular_lower),
                [
                    'post_id' => (int) $post_id,
                    'intent' => 'email_owner',
                    'sent' => true,
                ]
            );
        }
        $respond_error(sprintf(__('Could not send email to %s owner.', 'nexdirectory'), $singular_lower), 500);
    }


    public function render_moderation_queue($atts = []) {
        $atts = shortcode_atts([
            'pending_limit' => 100,
            'reports_limit' => 30,
            'admin_context' => 0,
        ], (array) $atts, 'eep_moderation_queue');
        $pending_limit = max(1, (int) $atts['pending_limit']);
        $reports_limit = max(1, (int) $atts['reports_limit']);
        $admin_context = (string) $atts['admin_context'] === '1';

        if (!$this->user_can_moderate_events()) {
            return '<p>' . esc_html__('Admin/editor access required.', 'nexdirectory') . '</p>';
        }

        $pending = new WP_Query([
            'post_type' => self::POST_TYPE,
            'post_status' => 'pending',
            'posts_per_page' => $pending_limit,
            'orderby' => 'date',
            'order' => 'ASC',
        ]);

        $reports = get_comments([
            'type' => 'eep_report',
            'status' => 'approve',
            'number' => $reports_limit,
            'orderby' => 'comment_date_gmt',
            'order' => 'DESC',
        ]);
        $claims = get_comments([
            'type' => 'eep_claim',
            'status' => 'hold',
            'number' => $reports_limit,
            'orderby' => 'comment_date_gmt',
            'order' => 'ASC',
        ]);
        $ui_labels = $this->get_site_profile_ui_labels();

        $output = $this->render_frontend_template('moderation-queue.php', [
            'pending' => $pending,
            'reports' => $reports,
            'claims' => $claims,
            'ui_labels' => $ui_labels,
            'admin_context' => $admin_context,
        ]);
        wp_reset_postdata();
        return $output;
    }


    public function handle_moderate_event() {
        if (!$this->user_can_moderate_events()) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }
        $post_id = $this->resolve_request_int_value($_POST, ['post_id', 'event_id']);
        if ($post_id <= 0 && isset($_POST['post_ids']) && is_array($_POST['post_ids'])) {
            $post_ids = array_values(array_filter(array_map('absint', (array) wp_unslash($_POST['post_ids']))));
            if (!empty($post_ids[0])) {
                $post_id = (int) $post_ids[0];
            }
        }
        $decision = $this->resolve_moderation_decision_from_request($_POST, ['moderation_action', 'decision', 'bulk_decision']);
        if ($decision === '') {
            foreach (['approve', 'reject', 'request_changes'] as $decision_key) {
                if (!empty($_POST[$decision_key])) {
                    $decision = $decision_key;
                    break;
                }
            }
        }
        $note = eep_sanitize_textarea(wp_unslash($_POST['moderator_note'] ?? ''));
        if (!$post_id || !in_array($decision, ['approve', 'reject', 'request_changes'], true)) {
            $this->append_runtime_log('warning', 'Invalid moderation request via form', [
                'post_id' => (string) $post_id,
                'decision' => (string) $decision,
                'user_id' => (string) get_current_user_id(),
            ]);
            $this->redirect_with_message(
                wp_get_referer() ?: home_url('/'),
                __('Invalid moderation request. Please select a valid action and event.', 'nexdirectory'),
                'error'
            );
        }
        if (!isset($_POST['eep_moderate_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_moderate_nonce'])), 'eep_moderate_event_' . $post_id)) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }
        $result = $this->apply_moderation_decision($post_id, $decision, $note);
        if (is_wp_error($result)) {
            $this->append_runtime_log('error', 'Moderation decision failed', [
                'post_id' => (string) $post_id,
                'decision' => (string) $decision,
                'error' => (string) $result->get_error_message(),
            ]);
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), $result->get_error_message(), 'error');
        }
        $this->redirect_with_message(wp_get_referer() ?: home_url('/'), (string) $result);
    }


    public function handle_moderate_events_bulk() {
        if (!$this->user_can_moderate_events()) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }
        if (!isset($_POST['eep_moderate_bulk_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_moderate_bulk_nonce'])), 'eep_moderate_events_bulk')) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }

        $decision = $this->resolve_moderation_decision_from_request($_POST, ['bulk_decision', 'decision', 'moderation_action']);
        $note = eep_sanitize_textarea((string) wp_unslash($_POST['moderator_note'] ?? ''));
        $post_ids = isset($_POST['post_ids']) && is_array($_POST['post_ids']) ? array_map('absint', (array) wp_unslash($_POST['post_ids'])) : [];
        $post_ids = array_values(array_unique(array_filter($post_ids)));

        if (empty($post_ids) || !in_array($decision, ['approve', 'reject', 'request_changes'], true)) {
            $this->append_runtime_log('warning', 'Invalid bulk moderation request via form', [
                'decision' => (string) $decision,
                'post_ids' => (string) wp_json_encode($post_ids),
                'user_id' => (string) get_current_user_id(),
            ]);
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Select events and a valid action for bulk moderation.', 'nexdirectory'), 'error');
        }

        $success = 0;
        $failed = 0;
        foreach ($post_ids as $post_id) {
            $result = $this->apply_moderation_decision((int) $post_id, $decision, $note);
            if (is_wp_error($result)) {
                $failed++;
            } else {
                $success++;
            }
        }

        $message = sprintf(
            __('Bulk moderation complete. Success: %1$d · Failed: %2$d.', 'nexdirectory'),
            (int) $success,
            (int) $failed
        );
        $this->redirect_with_message(
            wp_get_referer() ?: home_url('/'),
            $message,
            $failed > 0 ? 'error' : 'success'
        );
    }


    private function is_event_claimable($post_id) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return false;
        }
        if ((string) get_option('eep_enable_claim_listings', '0') !== '1') {
            return false;
        }
        $flag = (string) get_post_meta($post_id, 'eep_claimable', true);
        if ($flag === '1') {
            return true;
        }
        if ($flag === '0') {
            return false;
        }

        $owner_id = (int) get_post_field('post_author', $post_id);
        return $owner_id > 0 && user_can($owner_id, 'manage_options');
    }


    private function get_user_claim_status_for_event($post_id, $user_id) {
        $post_id = absint((int) $post_id);
        $user_id = absint((int) $user_id);
        if ($post_id <= 0 || $user_id <= 0) {
            return '';
        }
        $claims = get_comments([
            'post_id' => $post_id,
            'user_id' => $user_id,
            'type' => 'eep_claim',
            'orderby' => 'comment_date_gmt',
            'order' => 'DESC',
            'number' => 1,
            'status' => 'all',
        ]);
        if (empty($claims) || !isset($claims[0]) || !$claims[0] instanceof WP_Comment) {
            return '';
        }
        $status = (string) get_comment_meta((int) $claims[0]->comment_ID, 'eep_claim_status', true);
        if ($status === '') {
            $status = (string) $claims[0]->comment_approved === '1' ? 'approved' : 'pending';
        }
        return sanitize_key($status);
    }


    public function render_claim_listing($atts = []) {
        $atts = shortcode_atts([
            'event_id' => 0,
        ], (array) $atts, 'eep_claim_listing');
        $ui_labels = $this->get_site_profile_ui_labels();
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $singular_lower = sanitize_text_field((string) ($ui_labels['singular_lower'] ?? strtolower((string) $singular_label)));
        $plural_label = sanitize_text_field((string) ($ui_labels['plural'] ?? __('Events', 'nexdirectory')));

        if ((string) get_option('eep_enable_claim_listings', '0') !== '1') {
            return '<p>' . esc_html__('Listing claims are currently disabled.', 'nexdirectory') . '</p>';
        }
        if (!is_user_logged_in()) {
            return $this->redirect_to_login_screen(__('Please log in to claim a listing.', 'nexdirectory'));
        }

        $event_id = absint((int) $atts['event_id']);
        if ($event_id <= 0) {
            $event_id = absint((int) ($_GET['event_id'] ?? 0));
        }
        if ($event_id <= 0 || get_post_type($event_id) !== self::POST_TYPE) {
            return '<p>' . esc_html(sprintf(__('Select a valid %s listing to claim.', 'nexdirectory'), $singular_lower)) . '</p>';
        }

        $current_user_id = get_current_user_id();
        $event_owner_id = (int) get_post_field('post_author', $event_id);
        if ($event_owner_id === $current_user_id) {
            return '<p>' . esc_html__('You already own this listing.', 'nexdirectory') . '</p>';
        }
        if (!$this->is_event_claimable($event_id)) {
            return '<p>' . esc_html__('This listing is not currently available for claim.', 'nexdirectory') . '</p>';
        }

        $existing_claim_status = $this->get_user_claim_status_for_event($event_id, $current_user_id);
        $events_url = $this->get_platform_page_url(
            'eep_page_events_url',
            $this->get_profile_route_slugs('events'),
            $this->get_profile_route_default_url('events')
        );
        $start_raw = (string) get_post_meta($event_id, 'eep_start', true);
        $start_ts = $start_raw !== '' ? strtotime($start_raw) : false;
        $event_date = $start_ts ? $this->format_event_datetime_for_display($start_raw) : '';
        $location_parts = array_filter([
            (string) get_post_meta($event_id, 'eep_venue', 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) !== '';
        });

        return $this->render_frontend_template('claim-listing.php', [
            'event_id' => $event_id,
            'event_title' => get_the_title($event_id),
            'event_url' => get_permalink($event_id),
            'events_url' => $events_url,
            'event_cover' => $this->get_event_cover_image_url($event_id, 'large', 'thumb'),
            'event_date' => $event_date,
            'event_location' => implode(', ', array_map('eep_sanitize_text', $location_parts)),
            'claim_status' => $existing_claim_status,
            'claim_nonce_html' =>
                wp_nonce_field('eep_claim_listing_' . $event_id, 'eep_claim_listing_nonce', true, false)
                . wp_nonce_field('eep_claim_listing_' . $event_id, '_wpnonce', true, false),
            'ui_labels' => $ui_labels,
            'singular_label' => $singular_label,
            'singular_lower' => $singular_lower,
            'plural_label' => $plural_label,
        ]);
    }


    public function handle_claim_listing() {
        if (!is_user_logged_in()) {
            $redirect_back = wp_get_referer();
            if (!is_string($redirect_back) || trim($redirect_back) === '') {
                $redirect_back = home_url('/');
            }
            $login_url = $this->get_login_signup_url($redirect_back);
            $this->redirect_with_message($login_url, __('Please log in to claim a listing.', 'nexdirectory'), 'error');
        }
        $ui_labels = $this->get_site_profile_ui_labels();
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $singular_lower = sanitize_text_field((string) ($ui_labels['singular_lower'] ?? strtolower((string) $singular_label)));
        if ((string) get_option('eep_enable_claim_listings', '0') !== '1') {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Listing claims are disabled.', 'nexdirectory'), 'error');
        }

        $event_id = absint((int) wp_unslash($_POST['event_id'] ?? 0));
        if ($event_id <= 0 || get_post_type($event_id) !== self::POST_TYPE) {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), sprintf(__('Invalid %s selected.', 'nexdirectory'), $singular_lower), 'error');
        }
        $claim_nonce = eep_sanitize_text((string) wp_unslash($_POST['eep_claim_listing_nonce'] ?? ($_POST['_wpnonce'] ?? '')));
        if ($claim_nonce === '' || !wp_verify_nonce($claim_nonce, 'eep_claim_listing_' . $event_id)) {
            $this->redirect_with_message(
                get_permalink($event_id) ?: (wp_get_referer() ?: home_url('/')),
                __('Your claim session expired. Please try again.', 'nexdirectory'),
                'error'
            );
        }
        if (!$this->is_event_claimable($event_id)) {
            $this->redirect_with_message(get_permalink($event_id), sprintf(__('This %s is not available for claim.', 'nexdirectory'), $singular_lower), 'error');
        }

        $user_id = get_current_user_id();
        $owner_id = (int) get_post_field('post_author', $event_id);
        if ($owner_id === $user_id) {
            $this->redirect_with_message(get_permalink($event_id), __('You already own this listing.', 'nexdirectory'));
        }

        $existing_pending = get_comments([
            'post_id' => $event_id,
            'user_id' => $user_id,
            'type' => 'eep_claim',
            'status' => 'hold',
            'number' => 1,
            'orderby' => 'comment_date_gmt',
            'order' => 'DESC',
            'fields' => 'ids',
        ]);
        if (!empty($existing_pending)) {
            $this->redirect_with_message(get_permalink($event_id), sprintf(__('You already have a pending claim for this %s.', 'nexdirectory'), $singular_lower), 'error');
        }

        $claim_reason = eep_sanitize_textarea((string) wp_unslash($_POST['claim_reason'] ?? ''));
        $require_approval = (string) get_option('eep_claim_require_admin_approval', '1') === '1';
        $comment_approved = $require_approval ? '0' : '1';
        $comment_id = wp_insert_comment([
            'comment_post_ID' => $event_id,
            'comment_type' => 'eep_claim',
            'comment_content' => $claim_reason,
            'user_id' => $user_id,
            'comment_author' => wp_get_current_user()->display_name,
            'comment_author_email' => wp_get_current_user()->user_email,
            'comment_approved' => $comment_approved,
        ]);
        if (!$comment_id) {
            $this->redirect_with_message(get_permalink($event_id), __('Could not submit your claim request. Please try again.', 'nexdirectory'), 'error');
        }

        update_comment_meta((int) $comment_id, 'eep_claim_status', $require_approval ? 'pending' : 'approved');
        update_comment_meta((int) $comment_id, 'eep_claim_requested_at', current_time('mysql'));

        if (!$require_approval) {
            wp_update_post([
                'ID' => $event_id,
                'post_author' => $user_id,
            ]);
            update_post_meta($event_id, 'eep_claimable', '0');
            update_post_meta($event_id, 'eep_claimed_by', (string) $user_id);
            $this->append_runtime_log('info', 'Listing claim auto-approved', [
                'event_id' => (string) $event_id,
                'comment_id' => (string) $comment_id,
                'user_id' => (string) $user_id,
            ]);
            $this->redirect_with_message(get_permalink($event_id), __('Listing claimed successfully.', 'nexdirectory'), 'success');
        }

        $admin_email = sanitize_email((string) get_option('admin_email'));
        if (is_email($admin_email)) {
            $subject = sprintf(__('New listing claim request: %s', 'nexdirectory'), get_the_title($event_id));
            $moderation_url = admin_url('admin.php?page=eep-moderation');
            $body = sprintf(
                "A new claim request was submitted.\n\n%s: %s\nListing URL: %s\nClaimant: %s (%s)\nReason: %s\n\nModerate claim: %s",
                $singular_label,
                get_the_title($event_id),
                get_permalink($event_id),
                wp_get_current_user()->display_name,
                wp_get_current_user()->user_email,
                $claim_reason !== '' ? $claim_reason : __('No reason provided.', 'nexdirectory'),
                $moderation_url
            );
            $sent = (bool) $this->send_platform_email($admin_email, $subject, '', [
                'fallback_text' => $body,
            ]);
            if (!$sent) {
                $sent = (bool) wp_mail($admin_email, $subject, $body);
            }
            $this->append_runtime_log($sent ? 'info' : 'warning', 'Admin claim notification email ' . ($sent ? 'sent' : 'failed'), [
                'event_id' => (string) $event_id,
                'comment_id' => (string) $comment_id,
                'admin_email' => (string) $admin_email,
            ]);
        }

        $this->append_runtime_log('info', 'Listing claim submitted', [
            'event_id' => (string) $event_id,
            'comment_id' => (string) $comment_id,
            'user_id' => (string) $user_id,
        ]);
        $this->redirect_with_message(get_permalink($event_id), __('Claim submitted. An admin will review your request.', 'nexdirectory'));
    }


    public function handle_moderate_claim() {
        if (!$this->user_can_moderate_events()) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }
        $ui_labels = $this->get_site_profile_ui_labels();
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $singular_lower = sanitize_text_field((string) ($ui_labels['singular_lower'] ?? strtolower((string) $singular_label)));

        $comment_id = absint((int) wp_unslash($_POST['comment_id'] ?? 0));
        $decision = sanitize_key((string) wp_unslash($_POST['decision'] ?? ''));
        $reject_reason = eep_sanitize_textarea((string) wp_unslash($_POST['claim_reject_reason'] ?? ''));
        if ($comment_id <= 0 || !in_array($decision, ['approve', 'reject'], true)) {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Invalid claim moderation request.', 'nexdirectory'), 'error');
        }
        if (!isset($_POST['eep_moderate_claim_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_moderate_claim_nonce'])), 'eep_moderate_claim_' . $comment_id)) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }

        $comment = get_comment($comment_id);
        if (!$comment instanceof WP_Comment || (string) $comment->comment_type !== 'eep_claim') {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Claim request not found.', 'nexdirectory'), 'error');
        }

        $event_id = (int) $comment->comment_post_ID;
        $claim_user_id = (int) $comment->user_id;
        if ($event_id <= 0 || $claim_user_id <= 0 || get_post_type($event_id) !== self::POST_TYPE) {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Claim request data is invalid.', 'nexdirectory'), 'error');
        }
        if ($decision === 'reject' && trim($reject_reason) === '') {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Please provide a rejection reason so it can be emailed to the claimant.', 'nexdirectory'), 'error');
        }

        if ($decision === 'approve') {
            wp_update_post([
                'ID' => $event_id,
                'post_author' => $claim_user_id,
            ]);
            wp_set_comment_status($comment_id, 'approve');
            update_comment_meta($comment_id, 'eep_claim_status', 'approved');
            update_comment_meta($comment_id, 'eep_claim_reviewed_by', (string) get_current_user_id());
            update_comment_meta($comment_id, 'eep_claim_reviewed_at', current_time('mysql'));
            update_post_meta($event_id, 'eep_claimable', '0');
            update_post_meta($event_id, 'eep_claimed_by', (string) $claim_user_id);
            delete_comment_meta($comment_id, 'eep_claim_review_note');
            $message = sprintf(__('Claim approved and %s ownership transferred.', 'nexdirectory'), $singular_lower);
        } else {
            wp_set_comment_status($comment_id, 'approve');
            update_comment_meta($comment_id, 'eep_claim_status', 'rejected');
            update_comment_meta($comment_id, 'eep_claim_reviewed_by', (string) get_current_user_id());
            update_comment_meta($comment_id, 'eep_claim_reviewed_at', current_time('mysql'));
            update_comment_meta($comment_id, 'eep_claim_review_note', $reject_reason);
            $message = __('Claim rejected.', 'nexdirectory');
        }

        $claim_user = get_user_by('id', $claim_user_id);
        if ($claim_user instanceof WP_User && is_email((string) $claim_user->user_email)) {
            $subject = sprintf(__('Claim update for "%s"', 'nexdirectory'), get_the_title($event_id));
            $status_text = $decision === 'approve' ? __('approved', 'nexdirectory') : __('rejected', 'nexdirectory');
            $body = sprintf(
                __("Your claim for \"%1\$s\" was %2\$s.\n\nListing URL: %3\$s", 'nexdirectory'),
                get_the_title($event_id),
                $status_text,
                get_permalink($event_id)
            );
            if ($decision === 'reject' && trim($reject_reason) !== '') {
                $body .= "\n\n" . sprintf(
                    __('Reason from moderator: %s', 'nexdirectory'),
                    $reject_reason
                );
            }
            $this->send_platform_email((string) $claim_user->user_email, $subject, '', [
                'fallback_text' => $body,
                'email_heading' => __('Claim Status Update', 'nexdirectory'),
            ]);
        }

        $this->append_runtime_log('info', 'Claim moderation completed', [
            'event_id' => (string) $event_id,
            'comment_id' => (string) $comment_id,
            'decision' => (string) $decision,
            'reject_reason' => (string) $reject_reason,
            'moderator' => (string) get_current_user_id(),
        ]);
        $this->redirect_with_message(wp_get_referer() ?: home_url('/'), $message);
    }


    public function handle_moderate_report() {
        if (!$this->user_can_moderate_events()) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }

        $ui_labels = $this->get_site_profile_ui_labels();
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $singular_lower = sanitize_text_field((string) ($ui_labels['singular_lower'] ?? strtolower((string) $singular_label)));

        $comment_id = $this->resolve_request_int_value($_POST, ['comment_id', 'report_id']);
        $intent = sanitize_key((string) wp_unslash($_POST['intent'] ?? ''));
        if ($comment_id <= 0 || !in_array($intent, ['toggle_event_status', 'resolve_report', 'email_reporter', 'email_owner'], true)) {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Invalid report moderation request.', 'nexdirectory'), 'error');
        }
        if (!isset($_POST['eep_moderate_report_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_moderate_report_nonce'])), 'eep_moderate_report_' . $comment_id)) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }

        $comment = get_comment($comment_id);
        if (!$comment instanceof WP_Comment || (string) $comment->comment_type !== 'eep_report') {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Report not found.', 'nexdirectory'), 'error');
        }

        $event_id = (int) $comment->comment_post_ID;
        if ($event_id <= 0 || get_post_type($event_id) !== self::POST_TYPE) {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Report is linked to an invalid listing.', 'nexdirectory'), 'error');
        }

        if ($intent === 'resolve_report') {
            wp_set_comment_status($comment_id, 'hold');
            update_comment_meta($comment_id, 'eep_report_resolved_by', (string) get_current_user_id());
            update_comment_meta($comment_id, 'eep_report_resolved_at', current_time('mysql'));
            $this->append_runtime_log('info', 'Report resolved', [
                'comment_id' => (string) $comment_id,
                'event_id' => (string) $event_id,
                'moderator_id' => (string) get_current_user_id(),
            ]);
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Report marked as resolved.', 'nexdirectory'));
        }

        if ($intent === 'toggle_event_status') {
            $current_status = sanitize_key((string) get_post_status($event_id));
            $next_status = $current_status === 'publish' ? 'draft' : 'publish';
            $result = wp_update_post([
                'ID' => $event_id,
                'post_status' => $next_status,
            ], true);
            if (is_wp_error($result)) {
                $this->redirect_with_message(wp_get_referer() ?: home_url('/'), $result->get_error_message(), 'error');
            }
            $note = $next_status === 'publish'
                ? sprintf(__('%s reactivated after moderation review.', 'nexdirectory'), $singular_label)
                : sprintf(__('%s temporarily deactivated pending moderation review.', 'nexdirectory'), $singular_label);
            update_post_meta($event_id, 'eep_moderation_note', $note);
            update_post_meta($event_id, 'eep_moderation_action', $next_status === 'publish' ? 'approve' : 'reject');
            $this->notify_author_status($event_id, $next_status, $note);
            $this->append_runtime_log('info', 'Report toggle event status', [
                'comment_id' => (string) $comment_id,
                'event_id' => (string) $event_id,
                'old_status' => (string) $current_status,
                'new_status' => (string) $next_status,
                'moderator_id' => (string) get_current_user_id(),
            ]);
            $this->redirect_with_message(
                wp_get_referer() ?: home_url('/'),
                $next_status === 'publish'
                    ? sprintf(__('%s activated.', 'nexdirectory'), $singular_label)
                    : sprintf(__('%s deactivated and moved to draft.', 'nexdirectory'), $singular_label)
            );
        }

        $admin_subject = sanitize_text_field((string) wp_unslash($_POST['admin_subject'] ?? ''));
        $admin_message = eep_sanitize_textarea((string) wp_unslash($_POST['admin_message'] ?? ''));
        if (trim($admin_message) === '') {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Please provide a message before sending.', 'nexdirectory'), 'error');
        }

        $target_email = '';
        $target_label = '';
        $target_name = '';
        if ($intent === 'email_reporter') {
            if ((int) $comment->user_id > 0) {
                $report_user = get_user_by('id', (int) $comment->user_id);
                if ($report_user instanceof WP_User && is_email((string) $report_user->user_email)) {
                    $target_email = (string) $report_user->user_email;
                    $target_name = (string) ($report_user->display_name ?: $report_user->user_login);
                }
            }
            if ($target_email === '' && is_email((string) $comment->comment_author_email)) {
                $target_email = (string) $comment->comment_author_email;
                $target_name = sanitize_text_field((string) $comment->comment_author);
            }
            $target_label = __('reporter', 'nexdirectory');
            if ($admin_subject === '') {
                $admin_subject = sprintf(__('Update on your report for "%s"', 'nexdirectory'), get_the_title($event_id));
            }
        } else {
            $owner_id = (int) get_post_field('post_author', $event_id);
            $owner = $owner_id > 0 ? get_user_by('id', $owner_id) : null;
            if ($owner instanceof WP_User && is_email((string) $owner->user_email)) {
                $target_email = (string) $owner->user_email;
                $target_name = (string) ($owner->display_name ?: $owner->user_login);
            }
            $target_label = sprintf(__('%s owner', 'nexdirectory'), $singular_lower);
            if ($admin_subject === '') {
                $admin_subject = sprintf(__('Moderation note for "%s"', 'nexdirectory'), get_the_title($event_id));
            }
        }

        if ($target_email === '' || !is_email($target_email)) {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), sprintf(__('Could not determine a valid email for this %s.', 'nexdirectory'), $target_label), 'error');
        }

        $admin_user = wp_get_current_user();
        $headers = [];
        if ($admin_user instanceof WP_User && is_email((string) $admin_user->user_email)) {
            $headers[] = 'Reply-To: ' . $admin_user->display_name . ' <' . $admin_user->user_email . '>';
        }
        $fallback_text = sprintf(
            "Hello %s,\n\n%s\n\n%s: %s\nLink: %s",
            $target_name !== '' ? $target_name : __('there', 'nexdirectory'),
            $admin_message,
            $singular_label,
            get_the_title($event_id),
            get_permalink($event_id)
        );

        $sent = (bool) $this->send_platform_email($target_email, $admin_subject, '', [
            'fallback_text' => $fallback_text,
        ], $headers);
        if (!$sent) {
            $sent = (bool) wp_mail($target_email, $admin_subject, $fallback_text, $headers);
        }
        if (!$sent) {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), sprintf(__('Could not send message to the %s.', 'nexdirectory'), $target_label), 'error');
        }

        update_comment_meta($comment_id, 'eep_report_last_contacted_by', (string) get_current_user_id());
        update_comment_meta($comment_id, 'eep_report_last_contacted_at', current_time('mysql'));
        update_comment_meta($comment_id, 'eep_report_last_contact_target', $intent === 'email_reporter' ? 'reporter' : 'owner');
        $this->append_runtime_log('info', 'Report contact email sent', [
            'comment_id' => (string) $comment_id,
            'event_id' => (string) $event_id,
            'intent' => (string) $intent,
            'target_email' => (string) $target_email,
            'moderator_id' => (string) get_current_user_id(),
        ]);

        $this->redirect_with_message(
            wp_get_referer() ?: home_url('/'),
            $intent === 'email_reporter'
                ? __('Reply sent to reporter.', 'nexdirectory')
                : sprintf(__('Message sent to %s owner.', 'nexdirectory'), $singular_lower)
        );
    }


    private function apply_moderation_decision($post_id, $decision, $note = '') {
        $post_id = absint((int) $post_id);
        $decision = sanitize_key((string) $decision);
        $note = eep_sanitize_textarea((string) $note);
        $ui_labels = $this->get_site_profile_ui_labels();
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $singular_lower = sanitize_text_field((string) ($ui_labels['singular_lower'] ?? strtolower((string) $singular_label)));
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return new WP_Error('eep_moderation_invalid_post', sprintf(__('Invalid %s selected for moderation.', 'nexdirectory'), $singular_lower));
        }

        if ($decision === 'approve') {
            wp_update_post([
                'ID' => $post_id,
                'post_status' => 'publish',
            ]);
            update_post_meta($post_id, 'eep_moderation_note', $note);
            update_post_meta($post_id, 'eep_moderation_action', 'approve');
            $this->notify_author_status($post_id, 'publish', $note);
            return sprintf(__('%s approved.', 'nexdirectory'), $singular_label);
        }

        if ($decision === 'request_changes') {
            if ($note === '') {
                $note = __('Please update required details and resubmit for review.', 'nexdirectory');
            }
            wp_update_post([
                'ID' => $post_id,
                'post_status' => 'draft',
            ]);
            update_post_meta($post_id, 'eep_moderation_note', $note);
            update_post_meta($post_id, 'eep_moderation_action', 'request_changes');
            $this->notify_author_status($post_id, 'draft', $note);
            return sprintf(__('Changes requested. %s moved to draft.', 'nexdirectory'), $singular_label);
        }

        wp_update_post([
            'ID' => $post_id,
            'post_status' => 'draft',
        ]);
        update_post_meta($post_id, 'eep_moderation_note', $note);
        update_post_meta($post_id, 'eep_moderation_action', 'reject');
        $this->notify_author_status($post_id, 'draft', $note);
        return sprintf(__('%s rejected and moved to draft.', 'nexdirectory'), $singular_label);
    }


    private function resolve_moderation_decision_from_request($source, $keys = []) {
        if (!is_array($source) || empty($keys)) {
            return '';
        }
        foreach ($keys as $key) {
            $key = sanitize_key((string) $key);
            if ($key === '' || !array_key_exists($key, $source)) {
                continue;
            }
            $raw = wp_unslash($source[$key]);
            if (is_array($raw)) {
                foreach (array_reverse($raw) as $candidate) {
                    $normalized = sanitize_key((string) $candidate);
                    if ($normalized !== '') {
                        return $normalized;
                    }
                }
                continue;
            }
            $normalized = sanitize_key((string) $raw);
            if ($normalized !== '') {
                return $normalized;
            }
        }
        return '';
    }


    private function resolve_request_int_value($source, $keys = []) {
        if (!is_array($source) || empty($keys)) {
            return 0;
        }
        foreach ($keys as $key) {
            $key = sanitize_key((string) $key);
            if ($key === '' || !array_key_exists($key, $source)) {
                continue;
            }
            $raw = wp_unslash($source[$key]);
            if (is_array($raw)) {
                foreach ($raw as $candidate) {
                    $candidate_id = absint((int) $candidate);
                    if ($candidate_id > 0) {
                        return $candidate_id;
                    }
                }
                continue;
            }
            $candidate_id = absint((int) $raw);
            if ($candidate_id > 0) {
                return $candidate_id;
            }
        }
        return 0;
    }


    public function render_organizers($atts = []) {
        $atts = shortcode_atts([
            'limit' => 200,
            'verified_only' => 'no',
        ], (array) $atts, 'eep_organizers');
        $limit = max(1, (int) $atts['limit']);
        $verified_only = $atts['verified_only'] === 'yes';

        $users = get_users([
            'orderby' => 'display_name',
            'order' => 'ASC',
            'number' => $limit,
        ]);
        $ui_labels = $this->get_site_profile_ui_labels();

        return $this->render_frontend_template('organizers.php', [
            'users' => $users,
            'verified_only' => $verified_only,
            'ui_labels' => $ui_labels,
        ]);
    }


    public function render_auth_links($atts = []) {
        $atts = shortcode_atts([
            'redirect_to' => '',
            'show_logout' => 'yes',
            'class' => '',
        ], (array) $atts, 'eep_auth_links');

        $redirect_to = wp_validate_redirect(
            esc_url_raw((string) $atts['redirect_to']),
            $this->current_url()
        );
        $login_url = $this->get_auth_screen_url('login', $redirect_to);
        $signup_url = $this->get_auth_screen_url('signup', $redirect_to);

        $extra_classes_raw = preg_split('/\s+/', sanitize_text_field((string) $atts['class']));
        if (!is_array($extra_classes_raw)) {
            $extra_classes_raw = [];
        }
        $extra_classes = array_values(array_filter(array_map('sanitize_html_class', $extra_classes_raw)));
        $wrapper_classes = array_values(array_unique(array_filter(array_merge([
            'eep-page-auth-links',
            'eep-page-auth-links-shortcode',
        ], $extra_classes))));
        $wrapper_class = implode(' ', $wrapper_classes);

        if (!is_user_logged_in()) {
            return '<div class="' . esc_attr($wrapper_class) . '" role="navigation" aria-label="' . esc_attr__('Authentication', 'nexdirectory') . '">'
                . '<a class="eep-page-auth-link" href="' . esc_url($login_url) . '">' . esc_html__('Login', 'nexdirectory') . '</a>'
                . '<a class="eep-page-auth-link eep-page-auth-link-primary" href="' . esc_url($signup_url) . '">' . esc_html__('Sign Up', 'nexdirectory') . '</a>'
                . '</div>';
        }

        $profile_key = $this->get_site_profile_key();
        $account_url = $this->get_platform_page_url(
            'eep_page_account_url',
            $this->get_profile_route_slugs('account', $profile_key),
            $this->get_profile_route_default_url('account', $profile_key)
        );
        $my_events_url = $this->get_platform_page_url(
            'eep_page_my_events_url',
            $this->get_profile_route_slugs('my_items', $profile_key),
            $this->get_profile_route_default_url('my_items', $profile_key)
        );
        $notifications_url = $this->get_notifications_page_url();
        $create_event_url = $this->get_platform_page_url(
            'eep_page_submit_event_url',
            $this->get_profile_route_slugs('submit', $profile_key),
            $this->get_profile_route_default_url('submit', $profile_key)
        );
        $discover_events_url = $this->get_platform_page_url(
            'eep_page_events_url',
            $this->get_profile_route_slugs('events', $profile_key),
            $this->get_profile_route_default_url('events', $profile_key)
        );
        $whats_new_url = $this->get_platform_page_url(
            'eep_page_whats_new_url',
            ['whats-new'],
            home_url('/whats-new/')
        );
        $logout_url = wp_logout_url($login_url);
        $show_logout = strtolower((string) $atts['show_logout']) !== 'no';

        $menu_links = [
            [
                'label' => __('My Account', 'nexdirectory'),
                'url' => $account_url,
            ],
            [
                'label' => __('My Events', 'nexdirectory'),
                'url' => $my_events_url,
            ],
            [
                'label' => __('Inbox', 'nexdirectory'),
                'url' => $notifications_url,
            ],
            [
                'label' => __('Create Event', 'nexdirectory'),
                'url' => $create_event_url,
            ],
            [
                'label' => __('Discover Events', 'nexdirectory'),
                'url' => $discover_events_url,
            ],
            [
                'label' => __('What\'s New', 'nexdirectory'),
                'url' => $whats_new_url,
            ],
        ];
        if ($show_logout) {
            $menu_links[] = [
                'label' => __('Logout', 'nexdirectory'),
                'url' => $logout_url,
            ];
        }

        $output = '<div class="' . esc_attr($wrapper_class) . '" role="navigation" aria-label="' . esc_attr__('Account actions', 'nexdirectory') . '">';
        $output .= '<details class="eep-page-auth-menu">';
        $output .= '<summary class="eep-page-auth-link eep-page-auth-link-primary eep-page-auth-menu-toggle">' . esc_html__('My Account', 'nexdirectory') . '</summary>';
        $output .= '<div class="eep-page-auth-menu-panel">';
        foreach ($menu_links as $menu_link) {
            $url = esc_url((string) ($menu_link['url'] ?? ''));
            if ($url === '') {
                continue;
            }
            $output .= '<a class="eep-page-auth-menu-item" href="' . $url . '">' . esc_html((string) ($menu_link['label'] ?? '')) . '</a>';
        }
        $output .= '</div>';
        $output .= '</details>';
        $output .= '</div>';

        return $output;
    }


    public function render_auth($atts = []) {
        $atts = shortcode_atts([
            'redirect_to' => '',
        ], (array) $atts, 'eep_auth');

        $redirect_from_query = esc_url_raw((string) wp_unslash($_GET['redirect_to'] ?? ''));
        $global_redirect = esc_url_raw((string) get_option('eep_login_redirect_url', ''));
        if (!empty($atts['redirect_to'])) {
            $redirect = esc_url_raw((string) $atts['redirect_to']);
        } elseif ($redirect_from_query !== '') {
            $redirect = $redirect_from_query;
        } elseif (!empty($global_redirect)) {
            $redirect = $global_redirect;
        } else {
            $redirect = $this->current_url();
        }
        $redirect = wp_validate_redirect($redirect, home_url('/'));

        if (is_user_logged_in()) {
            $account_url = $this->get_platform_page_url('eep_page_account_url', 'account', home_url('/'));
            $auth_page_url = $this->get_platform_page_url('eep_page_login_signup_url', 'login-signup', home_url('/login-signup/'));
            $is_auth_target = false;
            if ($auth_page_url !== '') {
                $target_path = (string) wp_parse_url($redirect, PHP_URL_PATH);
                $auth_path = (string) wp_parse_url($auth_page_url, PHP_URL_PATH);
                $is_auth_target = $target_path !== '' && $auth_path !== '' && untrailingslashit($target_path) === untrailingslashit($auth_path);
            }
            if ($redirect === '' || $is_auth_target) {
                $redirect = $account_url;
            }
            $this->safe_redirect_or_fallback($redirect);
        }

        $default_tab = isset($_GET['eep_auth']) ? sanitize_key((string) wp_unslash($_GET['eep_auth'])) : 'login';
        if (!in_array($default_tab, ['login', 'signup', 'forgot', 'reset'], true)) {
            $default_tab = 'login';
        }
        $reset_login = sanitize_user((string) wp_unslash($_GET['login'] ?? ''), true);
        $reset_key = sanitize_text_field((string) wp_unslash($_GET['key'] ?? ''));
        $can_reset = false;
        if ($default_tab === 'reset') {
            if ($reset_login === '' || $reset_key === '') {
                $default_tab = 'forgot';
            } else {
                $reset_user = check_password_reset_key($reset_key, $reset_login);
                if (is_wp_error($reset_user)) {
                    $default_tab = 'forgot';
                } else {
                    $can_reset = true;
                }
            }
        }
        $ui_labels = $this->get_site_profile_ui_labels();
        $forgot_url = $this->get_auth_screen_url('forgot', $redirect);
        $login_url = $this->get_auth_screen_url('login', $redirect);
        $signup_url = $this->get_auth_screen_url('signup', $redirect);
        $reset_url = $can_reset ? $this->get_auth_reset_url($reset_login, $reset_key, $redirect) : '';

        return $this->render_frontend_template('auth.php', [
            'redirect' => $redirect,
            'default_tab' => $default_tab,
            'ui_labels' => $ui_labels,
            'forgot_url' => $forgot_url,
            'login_url' => $login_url,
            'signup_url' => $signup_url,
            'reset_url' => $reset_url,
            'can_reset' => $can_reset,
            'reset_login' => $reset_login,
            'reset_key' => $reset_key,
        ]);
    }


    private function get_auth_screen_url($tab = 'login', $redirect_to = '') {
        $tab = sanitize_key((string) $tab);
        if (!in_array($tab, ['login', 'signup', 'forgot', 'reset'], true)) {
            $tab = 'login';
        }
        $redirect_to = wp_validate_redirect(esc_url_raw((string) $redirect_to), home_url('/'));
        $custom_login_url = $this->get_custom_login_page_url();
        if ($custom_login_url === '') {
            if ($tab === 'forgot') {
                return wp_lostpassword_url($redirect_to);
            }
            return wp_login_url($redirect_to);
        }

        $args = ['eep_auth' => $tab];
        if ($redirect_to !== '') {
            $args['redirect_to'] = $redirect_to;
        }
        return eep_add_query_arg($args, $custom_login_url);
    }


    private function get_auth_reset_url($login, $key, $redirect_to = '') {
        $login = sanitize_user((string) $login, true);
        $key = sanitize_text_field((string) $key);
        $redirect_to = wp_validate_redirect(esc_url_raw((string) $redirect_to), home_url('/'));
        $custom_login_url = $this->get_custom_login_page_url();
        if ($custom_login_url === '') {
            $fallback = network_site_url('wp-login.php?action=rp', 'login');
            $args = [
                'key' => $key,
                'login' => $login,
            ];
            if ($redirect_to !== '') {
                $args['redirect_to'] = $redirect_to;
            }
            return eep_add_query_arg($args, $fallback);
        }

        $args = [
            'eep_auth' => 'reset',
            'login' => $login,
            'key' => $key,
        ];
        if ($redirect_to !== '') {
            $args['redirect_to'] = $redirect_to;
        }
        return eep_add_query_arg($args, $custom_login_url);
    }


    public function handle_forgot_password() {
        if (!isset($_POST['eep_auth_forgot_password_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_auth_forgot_password_nonce'])), 'eep_auth_forgot_password_nonce')) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }

        $redirect_target = wp_validate_redirect(
            esc_url_raw((string) wp_unslash($_POST['redirect_to'] ?? '')),
            home_url('/')
        );
        $forgot_url = $this->get_auth_screen_url('forgot', $redirect_target);
        $login_url = $this->get_auth_screen_url('login', $redirect_target);

        if (!empty($_POST['eep_website_auth']) || $this->is_submission_rate_limited()) {
            $this->increment_spam_counter();
            $this->redirect_with_message($forgot_url, __('Please wait and try again.', 'nexdirectory'), 'error');
        }

        $identifier = sanitize_text_field((string) wp_unslash($_POST['user_login_email'] ?? ''));
        if ($identifier === '') {
            $this->redirect_with_message($forgot_url, __('Enter your username or email address.', 'nexdirectory'), 'error');
        }

        $user = null;
        if (is_email($identifier)) {
            $user = get_user_by('email', sanitize_email($identifier));
        } else {
            $user = get_user_by('login', sanitize_user($identifier, true));
        }

        if ($user instanceof WP_User) {
            $reset_key = get_password_reset_key($user);
            if (!is_wp_error($reset_key)) {
                $reset_url = $this->get_auth_reset_url($user->user_login, (string) $reset_key, $redirect_target);
                $subject = sprintf(__('[%s] Reset your password', 'nexdirectory'), wp_specialchars_decode((string) $this->get_white_label_brand_name(), ENT_QUOTES));
                $fallback = sprintf(
                    "Hi %s,\n\nA password reset was requested for your account.\n\nReset your password here:\n%s\n\nIf you did not request this, you can ignore this email.",
                    $user->display_name ?: $user->user_login,
                    $reset_url
                );
                $this->send_platform_email(
                    $user->user_email,
                    $subject,
                    '',
                    ['fallback_text' => $fallback]
                );
            }
        }

        $this->redirect_with_message(
            $login_url,
            __('If an account exists for that username/email, a password reset link has been sent.', 'nexdirectory'),
            'success'
        );
    }


    public function handle_reset_password() {
        if (!isset($_POST['eep_auth_reset_password_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_auth_reset_password_nonce'])), 'eep_auth_reset_password_nonce')) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }

        $redirect_target = wp_validate_redirect(
            esc_url_raw((string) wp_unslash($_POST['redirect_to'] ?? '')),
            home_url('/')
        );

        if (!empty($_POST['eep_website_auth']) || $this->is_submission_rate_limited()) {
            $this->increment_spam_counter();
            $this->redirect_with_message($this->get_auth_screen_url('forgot', $redirect_target), __('Please wait and try again.', 'nexdirectory'), 'error');
        }

        $login = sanitize_user((string) wp_unslash($_POST['rp_login'] ?? ''), true);
        $key = sanitize_text_field((string) wp_unslash($_POST['rp_key'] ?? ''));
        $pass1 = (string) wp_unslash($_POST['pass1'] ?? '');
        $pass2 = (string) wp_unslash($_POST['pass2'] ?? '');

        if ($login === '' || $key === '') {
            $this->redirect_with_message($this->get_auth_screen_url('forgot', $redirect_target), __('Reset link is invalid or expired. Request a new one.', 'nexdirectory'), 'error');
        }

        $reset_url = $this->get_auth_reset_url($login, $key, $redirect_target);
        $user = check_password_reset_key($key, $login);
        if (is_wp_error($user) || !$user instanceof WP_User) {
            $this->redirect_with_message($this->get_auth_screen_url('forgot', $redirect_target), __('Reset link is invalid or expired. Request a new one.', 'nexdirectory'), 'error');
        }

        if ($pass1 === '' || $pass2 === '') {
            $this->redirect_with_message($reset_url, __('Please enter and confirm your new password.', 'nexdirectory'), 'error');
        }
        if ($pass1 !== $pass2) {
            $this->redirect_with_message($reset_url, __('Passwords do not match.', 'nexdirectory'), 'error');
        }
        if (strlen($pass1) < 8) {
            $this->redirect_with_message($reset_url, __('Password must be at least 8 characters.', 'nexdirectory'), 'error');
        }

        reset_password($user, $pass1);
        $this->redirect_with_message(
            $this->get_auth_screen_url('login', $redirect_target),
            __('Password reset successful. You can now log in.', 'nexdirectory'),
            'success'
        );
    }


    private function recaptcha_enabled() {
        return (string) get_option('eep_recaptcha_site_key', '') !== ''
            && (string) get_option('eep_recaptcha_secret_key', '') !== '';
    }


    private function should_enforce_submit_form_recaptcha() {
        if (!$this->recaptcha_enabled()) {
            return false;
        }
        if (is_user_logged_in() && current_user_can('manage_options')) {
            return false;
        }

        return (bool) $this->is_option_enabled('eep_recaptcha_show_submit', '0');
    }


    private function verify_recaptcha_token($token) {
        if (!$this->recaptcha_enabled()) {
            return true;
        }

        $token = sanitize_text_field((string) $token);
        if ($token === '') {
            return false;
        }

        $response = wp_remote_post('https://www.google.com/recaptcha/api/siteverify', [
            'timeout' => 12,
            'body' => [
                'secret' => (string) get_option('eep_recaptcha_secret_key', ''),
                'response' => $token,
                'remoteip' => sanitize_text_field((string) ($_SERVER['REMOTE_ADDR'] ?? '')),
            ],
        ]);

        if (is_wp_error($response)) {
            return false;
        }

        $code = (int) wp_remote_retrieve_response_code($response);
        if ($code < 200 || $code >= 300) {
            return false;
        }

        $json = json_decode((string) wp_remote_retrieve_body($response), true);
        return is_array($json) && !empty($json['success']);
    }

    private function get_add_event_redirect_url() {
        $profile_key = method_exists($this, 'get_site_profile_key')
            ? (string) $this->get_site_profile_key()
            : 'events';
        $redirect = $this->get_platform_page_url(
            'eep_page_submit_event_url',
            $this->get_profile_route_slugs('submit', $profile_key),
            $this->get_profile_route_default_url('submit', $profile_key)
        );
        $redirect = wp_validate_redirect((string) $redirect, home_url('/'));
        if (!is_string($redirect) || $redirect === '') {
            $redirect = home_url('/');
        }
        return $redirect;
    }

    private function consume_first_login_add_event_redirect($user_id) {
        $user_id = absint((int) $user_id);
        if ($user_id <= 0) {
            return '';
        }
        if (user_can($user_id, 'manage_options')) {
            return '';
        }
        $platform_mode = sanitize_key((string) get_option('eep_platform_mode', 'user_generated'));
        if ($platform_mode === 'single_organisation') {
            return '';
        }
        $pending = (string) get_user_meta($user_id, 'eep_first_login_force_submit', true);
        if ($pending !== '1') {
            return '';
        }
        update_user_meta($user_id, 'eep_first_login_force_submit', '0');
        update_user_meta($user_id, 'eep_first_login_submit_redirected_at', current_time('mysql'));
        return $this->get_add_event_redirect_url();
    }

    /**
     * Resolve the auth redirect target, preserving explicit intent when possible.
     *
     * Some flows (claim listing, SPA transitions, theme form overrides) may strip the
     * hidden redirect field; this falls back to request/referrer query values before
     * using the default redirect.
     *
     * @param string $fallback
     * @return array{0:string,1:bool}
     */
    private function resolve_auth_redirect_target($fallback = '') {
        $fallback = wp_validate_redirect(esc_url_raw((string) $fallback), home_url('/'));
        if (!is_string($fallback) || $fallback === '') {
            $fallback = home_url('/');
        }

        $candidates = [];
        $posted = wp_unslash($_POST['redirect_to'] ?? '');
        if (is_string($posted) && trim($posted) !== '') {
            $candidates[] = $posted;
        }

        $requested = wp_unslash($_REQUEST['redirect_to'] ?? '');
        if (is_string($requested) && trim($requested) !== '') {
            $candidates[] = $requested;
        }

        $referrer = wp_get_referer();
        if (is_string($referrer) && $referrer !== '') {
            $ref_query = (string) wp_parse_url($referrer, PHP_URL_QUERY);
            if ($ref_query !== '') {
                $ref_args = [];
                parse_str($ref_query, $ref_args);
                $ref_redirect = isset($ref_args['redirect_to']) ? wp_unslash($ref_args['redirect_to']) : '';
                if (is_string($ref_redirect) && trim($ref_redirect) !== '') {
                    $candidates[] = $ref_redirect;
                }
            }
        }

        foreach ($candidates as $candidate_raw) {
            $candidate = wp_validate_redirect(esc_url_raw((string) $candidate_raw), '');
            if (is_string($candidate) && $candidate !== '') {
                return [$candidate, true];
            }
        }

        return [$fallback, false];
    }


    public function handle_login() {
        $login_nonce = eep_sanitize_text((string) wp_unslash($_POST['eep_auth_login_nonce'] ?? ($_POST['_wpnonce'] ?? '')));
        if ($login_nonce === '' || !wp_verify_nonce($login_nonce, 'eep_auth_login_nonce')) {
            $fallback_after_login = wp_get_referer();
            if (!is_string($fallback_after_login) || trim($fallback_after_login) === '') {
                $fallback_after_login = home_url('/');
            }
            $this->redirect_with_message(
                $this->get_login_signup_url($fallback_after_login),
                __('Your login session expired. Please try again.', 'nexdirectory'),
                'error'
            );
        }
        if (!empty($_POST['eep_website_auth']) || $this->is_submission_rate_limited()) {
            $this->increment_spam_counter();
            wp_die(__('Please wait and try again.', 'nexdirectory'));
        }
        $recaptcha_valid = $this->verify_recaptcha_token(wp_unslash($_POST['eep_recaptcha_token'] ?? ''));

        $creds = [
            'user_login' => eep_sanitize_text(wp_unslash($_POST['log'] ?? '')),
            'user_password' => wp_unslash($_POST['pwd'] ?? ''),
            'remember' => true,
        ];

        $user = wp_signon($creds, is_ssl());
        $fallback_redirect = esc_url_raw((string) get_option('eep_login_redirect_url', ''));
        if (!$fallback_redirect) {
            $fallback_redirect = home_url('/');
        }
        list($redirect, $has_explicit_redirect) = $this->resolve_auth_redirect_target($fallback_redirect);

        if (!$recaptcha_valid) {
            $allow_admin_bypass = $user instanceof WP_User && user_can($user, 'manage_options');
            if (!$allow_admin_bypass) {
                if ($user instanceof WP_User) {
                    wp_clear_auth_cookie();
                }
                $this->increment_spam_counter();
                wp_die(__('reCAPTCHA validation failed.', 'nexdirectory'));
            }
        }

        if (is_wp_error($user)) {
            $this->redirect_with_message($redirect, $user->get_error_message(), 'error');
        }

        $first_login_redirect = $has_explicit_redirect ? '' : $this->consume_first_login_add_event_redirect((int) $user->ID);
        if ($first_login_redirect !== '') {
            $redirect = $first_login_redirect;
        }

        $this->safe_redirect_or_fallback($redirect);
    }


    public function handle_signup() {
        $signup_nonce = eep_sanitize_text((string) wp_unslash($_POST['eep_auth_signup_nonce'] ?? ($_POST['_wpnonce'] ?? '')));
        if ($signup_nonce === '' || !wp_verify_nonce($signup_nonce, 'eep_auth_signup_nonce')) {
            $fallback_after_signup = wp_get_referer();
            if (!is_string($fallback_after_signup) || trim($fallback_after_signup) === '') {
                $fallback_after_signup = home_url('/');
            }
            $this->redirect_with_message(
                $this->get_login_signup_url($fallback_after_signup),
                __('Your signup session expired. Please try again.', 'nexdirectory'),
                'error'
            );
        }
        if (!empty($_POST['eep_website_auth']) || $this->is_submission_rate_limited()) {
            $this->increment_spam_counter();
            wp_die(__('Please wait and try again.', 'nexdirectory'));
        }
        if (!$this->verify_recaptcha_token(wp_unslash($_POST['eep_recaptcha_token'] ?? ''))) {
            wp_die(__('reCAPTCHA validation failed.', 'nexdirectory'));
        }

        $fallback_redirect = esc_url_raw((string) get_option('eep_login_redirect_url', ''));
        if (!$fallback_redirect) {
            $fallback_redirect = home_url('/');
        }
        list($resolved_redirect, $has_explicit_redirect) = $this->resolve_auth_redirect_target($fallback_redirect);
        $error_redirect = $this->get_auth_screen_url('signup', $has_explicit_redirect ? $resolved_redirect : '');
        $user_login = sanitize_user(wp_unslash($_POST['user_login'] ?? ''), true);
        $user_email = sanitize_email(wp_unslash($_POST['user_email'] ?? ''));
        $user_pass = wp_unslash($_POST['user_pass'] ?? '');
        $gdpr_accept = (string) wp_unslash($_POST['eep_gdpr_accept'] ?? '0') === '1';
        $gdpr_marketing_opt_in = (string) wp_unslash($_POST['eep_gdpr_marketing_opt_in'] ?? '0') === '1';

        if (!$gdpr_accept) {
            $this->redirect_with_message(
                $error_redirect,
                __('You must agree to GDPR and essential event-account emails to sign up.', 'nexdirectory'),
                'error'
            );
        }
        if ($user_login === '' || strlen($user_login) < 3 || $this->suspicious_signup_content($user_login)) {
            $this->increment_spam_counter();
            $this->redirect_with_message($error_redirect, __('Username failed security validation.', 'nexdirectory'), 'error');
        }
        if (!$this->valid_signup_email($user_email)) {
            $this->increment_spam_counter();
            $this->redirect_with_message($error_redirect, __('Email failed validation. Use a real mailbox domain.', 'nexdirectory'), 'error');
        }

        $user_id = wp_create_user($user_login, $user_pass, $user_email);
        $redirect = $resolved_redirect;
        if (!$has_explicit_redirect) {
            $account_url = $this->get_platform_page_url(
                'eep_page_account_url',
                $this->get_profile_route_slugs('account', $this->get_site_profile_key()),
                $this->get_profile_route_default_url('account', $this->get_site_profile_key())
            );
            $account_url = wp_validate_redirect($account_url, '');
            if (is_string($account_url) && $account_url !== '') {
                $redirect = $account_url;
            }
        }

        if (is_wp_error($user_id)) {
            $this->redirect_with_message($error_redirect, $user_id->get_error_message(), 'error');
        }

        $platform_mode = sanitize_key((string) get_option('eep_platform_mode', 'user_generated'));
        $user = get_user_by('id', $user_id);
        if ($user) {
            $signup_role = $platform_mode === 'single_organisation' ? 'subscriber' : 'event_submitter';
            $user->set_role($signup_role);
            $force_first_submit_redirect = $platform_mode !== 'single_organisation' || user_can($user_id, 'manage_options');
            update_user_meta($user_id, 'eep_first_login_force_submit', $force_first_submit_redirect ? '1' : '0');
            $this->mark_user_gdpr_terms_accepted($user_id);
            $this->update_user_organizer_communication_consent($user_id, $gdpr_marketing_opt_in);
            if ((string) get_option('eep_require_email_verification', '1') === '1') {
                update_user_meta($user_id, 'eep_email_verified', '0');
                if ($this->send_verification_email($user_id)) {
                    update_user_meta($user_id, 'eep_email_verify_last_sent', (string) time());
                }
            } else {
                update_user_meta($user_id, 'eep_email_verified', '1');
            }
            wp_set_current_user($user_id);
            wp_set_auth_cookie($user_id);
        }

        $first_login_redirect = $has_explicit_redirect ? '' : $this->consume_first_login_add_event_redirect($user_id);
        if ($first_login_redirect !== '') {
            $redirect = $first_login_redirect;
        }

        $this->redirect_with_message(
            $redirect,
            (string) get_option('eep_require_email_verification', '1') === '1'
                ? (
                    $platform_mode === 'single_organisation'
                        ? __('Account created. Check your email to verify your account.', 'nexdirectory')
                        : __('Account created. Check your email to verify before first submission.', 'nexdirectory')
                )
                : __('Account created successfully.', 'nexdirectory')
        );
    }


    public function handle_resend_verification() {
        if (!is_user_logged_in()) {
            wp_die(__('Login required.', 'nexdirectory'));
        }
        if (!$this->user_can_access_platform(get_current_user_id())) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }
        if ((string) get_option('eep_require_email_verification', '1') !== '1') {
            $this->redirect_with_message(wp_get_referer() ?: home_url('/'), __('Email verification is disabled.', 'nexdirectory'));
        }
        if (!isset($_POST['eep_resend_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_resend_nonce'])), 'eep_resend_verification_nonce')) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }
        $ok = $this->send_verification_email(get_current_user_id());
        if ($ok) {
            update_user_meta(get_current_user_id(), 'eep_email_verify_last_sent', (string) time());
        }
        $this->redirect_with_message(
            wp_get_referer() ?: home_url('/'),
            $ok ? __('Verification email sent.', 'nexdirectory') : __('Could not send verification email.', 'nexdirectory'),
            $ok ? 'success' : 'error'
        );
    }


    public function handle_import_events() {
        if (!current_user_can('manage_options')) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }
        if (!isset($_POST['eep_import_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_import_nonce'])), 'eep_import_events_nonce')) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }
        $step = sanitize_key(wp_unslash($_POST['eep_import_step'] ?? 'preview'));
        $default_status = sanitize_key(wp_unslash($_POST['eep_import_default_status'] ?? 'pending'));
        if (!in_array($default_status, ['pending', 'draft', 'publish'], true)) {
            $default_status = 'pending';
        }
        $skip_duplicates = (string) (wp_unslash($_POST['eep_import_skip_duplicates'] ?? '0')) === '1';
        $import_page = admin_url('admin.php?page=eep-import');

        if ($step === 'apply') {
            $token = sanitize_key(wp_unslash($_POST['eep_import_token'] ?? ''));
            if ($token === '') {
                $this->redirect_with_message($import_page, __('Import preview token is missing. Please restart import.', 'nexdirectory'), 'error');
            }
            $payload = get_transient($this->get_import_preview_transient_key($token));
            if (!is_array($payload) || (int) ($payload['user_id'] ?? 0) !== get_current_user_id()) {
                $this->redirect_with_message($import_page, __('Import preview expired. Please upload the file again.', 'nexdirectory'), 'error');
            }

            $rows = (array) ($payload['rows'] ?? []);
            if (empty($rows)) {
                delete_transient($this->get_import_preview_transient_key($token));
                $this->redirect_with_message($import_page, __('No rows available for import. Please upload again.', 'nexdirectory'), 'error');
            }

            $allowed_fields = array_keys($this->get_import_supported_fields());
            $mapping = is_array($payload['mapping'] ?? null) ? (array) $payload['mapping'] : [];
            $posted_mapping = isset($_POST['eep_import_map']) && is_array($_POST['eep_import_map']) ? (array) wp_unslash($_POST['eep_import_map']) : [];
            foreach ($allowed_fields as $field_key) {
                if (array_key_exists($field_key, $posted_mapping)) {
                    $mapping[$field_key] = sanitize_key((string) $posted_mapping[$field_key]);
                }
            }
            if (empty($mapping['title'])) {
                $this->redirect_with_message($import_page . '&eep_import_preview=' . rawurlencode($token), __('Map at least one source column to "title".', 'nexdirectory'), 'error');
            }

            $success = 0;
            $duplicates = 0;
            $failed = 0;
            $errors = [];
            foreach ($rows as $idx => $row) {
                $mapped_row = $this->map_import_row_with_mapping($row, $mapping);
                $result = $this->import_event_from_row($mapped_row, $default_status, $skip_duplicates);
                if (is_wp_error($result)) {
                    if ($result->get_error_code() === 'duplicate_event') {
                        $duplicates++;
                        continue;
                    }
                    $failed++;
                    if (count($errors) < 5) {
                        $errors[] = sprintf('#%d: %s', (int) $idx + 1, $result->get_error_message());
                    }
                    if ($result->get_error_code() === 'demo_event_limit_reached') {
                        break;
                    }
                } else {
                    $success++;
                }
            }

            delete_transient($this->get_import_preview_transient_key($token));
            if ($success > 0) {
                $total_imports = (int) get_option('eep_import_events_count', 0);
                update_option('eep_import_events_count', $total_imports + $success, false);
            }

            $msg = sprintf(
                __('Import complete. Success: %1$d, Skipped duplicates: %2$d, Failed: %3$d', 'nexdirectory'),
                $success,
                $duplicates,
                $failed
            );
            if (!empty($errors)) {
                $msg .= ' | ' . implode(' ; ', $errors);
            }
            $this->redirect_with_message($import_page, $msg, $failed > 0 ? 'error' : 'success');
        }

        if (empty($_FILES['eep_import_file']) || !is_array($_FILES['eep_import_file'])) {
            $this->redirect_with_message($import_page, __('Please upload a file.', 'nexdirectory'), 'error');
        }
        $file = $_FILES['eep_import_file'];
        if (!empty($file['error']) || empty($file['tmp_name'])) {
            $this->redirect_with_message($import_page, __('Upload failed. Please try again.', 'nexdirectory'), 'error');
        }

        $ext = strtolower(pathinfo((string) ($file['name'] ?? ''), PATHINFO_EXTENSION));
        $rows = $this->parse_import_payload($ext, (string) $file['tmp_name']);
        if (is_wp_error($rows)) {
            $this->redirect_with_message($import_page, $rows->get_error_message(), 'error');
        }
        if (!is_array($rows) || empty($rows)) {
            $this->redirect_with_message($import_page, __('No rows found in import file.', 'nexdirectory'), 'error');
        }

        $normalized_rows = array_map([$this, 'normalize_import_row'], $rows);
        $max_rows = 5000;
        if (count($normalized_rows) > $max_rows) {
            $this->redirect_with_message(
                $import_page,
                sprintf(__('Import exceeds the limit of %d rows per file.', 'nexdirectory'), $max_rows),
                'error'
            );
        }
        $headers = $this->extract_import_headers($normalized_rows);
        if (empty($headers)) {
            $this->redirect_with_message($import_page, __('No valid headers detected in import file.', 'nexdirectory'), 'error');
        }

        $token = sanitize_key(wp_generate_password(20, false, false));
        $preview_payload = [
            'user_id' => get_current_user_id(),
            'rows' => $normalized_rows,
            'row_count' => count($normalized_rows),
            'headers' => $headers,
            'sample_rows' => array_slice($normalized_rows, 0, 5),
            'mapping' => $this->guess_import_mapping($headers),
            'default_status' => $default_status,
            'skip_duplicates' => $skip_duplicates ? '1' : '0',
            'created_at' => time(),
        ];
        set_transient($this->get_import_preview_transient_key($token), $preview_payload, 30 * MINUTE_IN_SECONDS);

        $this->safe_redirect_or_fallback($import_page . '&eep_import_preview=' . rawurlencode($token));
    }


    public function handle_import_demo_events() {
        if (!current_user_can('manage_options')) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }
        if (!isset($_POST['eep_import_demo_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_import_demo_nonce'])), 'eep_import_demo_events_nonce')) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }
        $redirect_url = admin_url('admin.php?page=eep-import');
        $requested_redirect = isset($_POST['eep_redirect_to']) ? esc_url_raw((string) wp_unslash($_POST['eep_redirect_to'])) : '';
        if ($requested_redirect !== '') {
            $redirect_url = wp_validate_redirect($requested_redirect, $redirect_url);
        }

        $requested_count = isset($_POST['eep_demo_count']) ? absint((int) $_POST['eep_demo_count']) : 50;
        if ($requested_count <= 0) {
            $requested_count = 50;
        }
        $platform_mode = sanitize_key((string) get_option('eep_platform_mode', 'user_generated'));
        $default_profile_key = $this->get_demo_profile_key_for_platform_mode($platform_mode);
        if ($default_profile_key === '') {
            $default_profile_key = 'events';
        }
        $requested_profile = sanitize_key((string) wp_unslash($_POST['eep_demo_profile'] ?? $default_profile_key));
        $demo_profile = $platform_mode === 'single_organisation' ? $default_profile_key : $requested_profile;
        if ($demo_profile === '') {
            $demo_profile = $default_profile_key;
        }
        $profile_config = $this->get_demo_profile_config($demo_profile);
        $rows = $this->build_demo_import_rows($requested_count, $demo_profile);
        $created = 0;
        $duplicates = 0;
        $failed = 0;
        $errors = [];
        $created_post_ids = [];
        $created_rows_by_post_id = [];

        $single_org_demo = $platform_mode === 'single_organisation' || $demo_profile === 'events_single_organisation';
        if ($single_org_demo) {
            $demo_owner_cycle = [max(1, (int) get_current_user_id())];
        } else {
            $demo_user_pool = $this->get_demo_seed_user_pool(get_current_user_id());
            $demo_owner_cycle = array_values(array_filter(array_unique(array_map('absint', (array) ($demo_user_pool['organizers'] ?? [])))));
            if (empty($demo_owner_cycle)) {
                $demo_owner_cycle = [max(1, (int) get_current_user_id())];
            }
        }
        $demo_owner_index = 0;

        foreach ($rows as $index => $row) {
            $target_owner_id = (int) ($demo_owner_cycle[$demo_owner_index % count($demo_owner_cycle)] ?? 0);
            if ($target_owner_id <= 0) {
                $target_owner_id = (int) get_current_user_id();
            }
            $result = $this->import_event_from_row($row, 'publish', true, $target_owner_id, false);
            $demo_owner_index++;
            if (is_wp_error($result)) {
                if ($result->get_error_code() === 'duplicate_event') {
                    $duplicates++;
                    continue;
                }
                $failed++;
                if (count($errors) < 5) {
                    $errors[] = sprintf('#%d: %s', (int) $index + 1, $result->get_error_message());
                }
                if ($result->get_error_code() === 'demo_event_limit_reached') {
                    break;
                }
                continue;
            }

            $post_id = absint((int) $result);
            if ($post_id <= 0) {
                $failed++;
                continue;
            }

            $this->apply_demo_import_post_enrichment($post_id, $row, (int) $index);
            $created++;
            $created_post_ids[] = $post_id;
            $created_rows_by_post_id[$post_id] = is_array($row) ? $row : [];
        }

        if ($created > 0) {
            $total_imports = (int) get_option('eep_import_events_count', 0);
            update_option('eep_import_events_count', $total_imports + $created, false);
        }
        $apply_presets = (string) wp_unslash($_POST['eep_demo_apply_presets'] ?? '1') === '1';
        if ($apply_presets) {
            $this->apply_demo_profile_settings($profile_config);
        }

        $seeded_summary = [];
        if (!empty($created_post_ids)) {
            $seeded_summary = $this->seed_demo_import_runtime_data($created_post_ids, $created_rows_by_post_id, get_current_user_id());
        }

        $message = sprintf(
            __('Demo import complete (%4$s). Created: %1$d, Skipped duplicates: %2$d, Failed: %3$d.', 'nexdirectory'),
            $created,
            $duplicates,
            $failed,
            (string) ($profile_config['label'] ?? __('Default', 'nexdirectory'))
        );
        if (!empty($seeded_summary) && is_array($seeded_summary)) {
            $seed_bits = [];
            $bit_map = [
                'analytics_rows' => __('analytics rows', 'nexdirectory'),
                'boost_orders' => __('boost orders', 'nexdirectory'),
                'messages' => __('messages', 'nexdirectory'),
                'threads' => __('conversations', 'nexdirectory'),
                'rsvps' => __('saved/attending rows', 'nexdirectory'),
                'ticket_orders' => __('ticket orders', 'nexdirectory'),
                'pending_posts' => __('pending submissions', 'nexdirectory'),
                'draft_posts' => __('draft submissions', 'nexdirectory'),
                'claim_requests' => __('claim requests', 'nexdirectory'),
                'reports' => __('user reports', 'nexdirectory'),
                'webapp_events' => __('web apps enabled', 'nexdirectory'),
                'webapp_chat' => __('web app chat messages', 'nexdirectory'),
                'webapp_qa' => __('web app Q&A items', 'nexdirectory'),
                'webapp_polls' => __('web app polls', 'nexdirectory'),
                'webapp_notifications' => __('web app notifications', 'nexdirectory'),
                'webapp_team_members' => __('team access members', 'nexdirectory'),
                'webapp_itineraries' => __('custom itineraries', 'nexdirectory'),
            ];
            foreach ($bit_map as $bit_key => $bit_label) {
                $bit_value = isset($seeded_summary[$bit_key]) ? absint((int) $seeded_summary[$bit_key]) : 0;
                if ($bit_value <= 0) {
                    continue;
                }
                $seed_bits[] = $bit_value . ' ' . $bit_label;
            }
            if (!empty($seed_bits)) {
                $message .= ' ' . sprintf(
                    __('Runtime demo data seeded: %s.', 'nexdirectory'),
                    implode(', ', $seed_bits)
                );
            }
        }
        if (!empty($errors)) {
            $message .= ' | ' . implode(' ; ', $errors);
        }

        $this->redirect_with_message(
            $redirect_url,
            $message,
            $failed > 0 ? 'error' : 'success'
        );
    }


    private function get_demo_profile_key_for_platform_mode($platform_mode = '') {
        $platform_mode = sanitize_key((string) $platform_mode);
        if ($platform_mode === '') {
            $platform_mode = sanitize_key((string) get_option('eep_platform_mode', 'user_generated'));
        }
        if ($platform_mode === 'single_organisation') {
            return 'events_single_organisation';
        }

        $site_profile_key = method_exists($this, 'get_site_profile_key') ? (string) $this->get_site_profile_key() : 'events';
        if ($site_profile_key === '') {
            $site_profile_key = 'events';
        }
        $site_profile = method_exists($this, 'get_site_profile_config') ? (array) $this->get_site_profile_config($site_profile_key) : [];
        $demo_profile = sanitize_key((string) ($site_profile['demo_profile'] ?? $site_profile_key));
        if ($demo_profile === '') {
            $demo_profile = 'events';
        }

        return $demo_profile;
    }


    private function get_demo_profile_catalog() {
        $site_profiles = method_exists($this, 'get_site_profile_catalog') ? (array) $this->get_site_profile_catalog() : [];
        if (empty($site_profiles)) {
            $site_profiles = [
                'events' => [
                    'label' => __('Events (default)', 'nexdirectory'),
                    'description' => __('Classic event discovery, map, RSVP, and calendar flows.', 'nexdirectory'),
                    'hero_kicker' => __('Event Event', 'nexdirectory'),
                ],
            ];
        }

        $presets = $this->get_demo_profile_presets();
        $default_preset = $presets['events'] ?? [
            'accent' => '#FF5E14',
            'accent_hover' => '#E04F0D',
            'hero_layout' => 'cinematic',
            'page_layout' => 'modern',
            'map_layout' => 'three-column',
            'map_density' => 'comfortable',
            'map_provider' => 'openstreetmap',
            'map_style' => 'osm-standard',
        ];

        $catalog = [];
        foreach ($site_profiles as $profile_key => $profile_config) {
            $profile_key = sanitize_key((string) $profile_key);
            if ($profile_key === '') {
                continue;
            }

            $profile_config = is_array($profile_config) ? $profile_config : [];
            $preset = isset($presets[$profile_key]) && is_array($presets[$profile_key]) ? $presets[$profile_key] : $default_preset;
            $site_profile = method_exists($this, 'get_site_profile_config') ? (array) $this->get_site_profile_config($profile_key) : [];

            $hero_kicker = sanitize_text_field((string) ($site_profile['hero_kicker'] ?? ($profile_config['hero_kicker'] ?? '')));
            if ($hero_kicker === '') {
                $hero_kicker = sanitize_text_field((string) ($profile_config['hero_kicker'] ?? __('Directory Listing', 'nexdirectory')));
            }

            $catalog[$profile_key] = [
                'label' => sanitize_text_field((string) ($profile_config['label'] ?? $profile_key)),
                'description' => sanitize_text_field((string) ($profile_config['description'] ?? '')),
                'site_profile' => $profile_key,
                'accent' => sanitize_hex_color((string) ($preset['accent'] ?? '#FF5E14')) ?: '#FF5E14',
                'accent_hover' => sanitize_hex_color((string) ($preset['accent_hover'] ?? '#E04F0D')) ?: '#E04F0D',
                'hero_layout' => sanitize_key((string) ($preset['hero_layout'] ?? 'cinematic')),
                'page_layout' => sanitize_key((string) ($preset['page_layout'] ?? 'modern')),
                'map_layout' => sanitize_key((string) ($preset['map_layout'] ?? 'three-column')),
                'map_density' => sanitize_key((string) ($preset['map_density'] ?? 'comfortable')),
                'map_provider' => sanitize_key((string) ($preset['map_provider'] ?? 'openstreetmap')),
                'map_style' => sanitize_key((string) ($preset['map_style'] ?? 'osm-standard')),
                'hero_kicker' => $hero_kicker,
                'topics' => $this->get_demo_profile_topics($profile_key),
                'media_pack' => $this->get_demo_profile_media_pack($profile_key),
            ];
        }
        $single_org_preset = isset($presets['events_single_organisation']) && is_array($presets['events_single_organisation'])
            ? (array) $presets['events_single_organisation']
            : (isset($presets['events']) && is_array($presets['events']) ? (array) $presets['events'] : $default_preset);
        $single_org_hero_kicker = __('Official Event Programme', 'nexdirectory');
        $catalog['events_single_organisation'] = [
            'label' => sanitize_text_field(__('Single-Organisation Events', 'nexdirectory')),
            'description' => sanitize_text_field(__('Admin-operated event programme with official sessions, ticket-ready defaults, and single-owner workflows.', 'nexdirectory')),
            'site_profile' => 'events',
            'accent' => sanitize_hex_color((string) ($single_org_preset['accent'] ?? '#1D4ED8')) ?: '#1D4ED8',
            'accent_hover' => sanitize_hex_color((string) ($single_org_preset['accent_hover'] ?? '#1E40AF')) ?: '#1E40AF',
            'hero_layout' => sanitize_key((string) ($single_org_preset['hero_layout'] ?? 'cinematic')),
            'page_layout' => sanitize_key((string) ($single_org_preset['page_layout'] ?? 'modern')),
            'map_layout' => sanitize_key((string) ($single_org_preset['map_layout'] ?? 'three-column')),
            'map_density' => sanitize_key((string) ($single_org_preset['map_density'] ?? 'comfortable')),
            'map_provider' => sanitize_key((string) ($single_org_preset['map_provider'] ?? 'openstreetmap')),
            'map_style' => sanitize_key((string) ($single_org_preset['map_style'] ?? 'osm-standard')),
            'hero_kicker' => sanitize_text_field($single_org_hero_kicker),
            'topics' => $this->get_demo_profile_topics('events_single_organisation'),
            'media_pack' => $this->get_demo_profile_media_pack('events_single_organisation'),
        ];

        return $catalog;
    }


    private function get_demo_profile_presets() {
        return [
            'events' => ['accent' => '#FF5E14', 'accent_hover' => '#E04F0D', 'hero_layout' => 'cinematic', 'page_layout' => 'modern', 'map_layout' => 'three-column', 'map_density' => 'comfortable', 'map_provider' => 'openstreetmap', 'map_style' => 'osm-standard'],
            'events_single_organisation' => ['accent' => '#1D4ED8', 'accent_hover' => '#1E40AF', 'hero_layout' => 'cinematic', 'page_layout' => 'modern', 'map_layout' => 'three-column', 'map_density' => 'comfortable', 'map_provider' => 'openstreetmap', 'map_style' => 'carto-light'],
            'directory' => ['accent' => '#F97316', 'accent_hover' => '#EA580C', 'hero_layout' => 'split', 'page_layout' => 'fifty-fifty', 'map_layout' => 'below-grid-large', 'map_density' => 'compact', 'map_provider' => 'openstreetmap', 'map_style' => 'carto-light'],
            'local_businesses' => ['accent' => '#F97316', 'accent_hover' => '#EA580C', 'hero_layout' => 'split', 'page_layout' => 'fifty-fifty', 'map_layout' => 'below-grid-large', 'map_density' => 'compact', 'map_provider' => 'openstreetmap', 'map_style' => 'carto-light'],
            'trades_contractors' => ['accent' => '#F59E0B', 'accent_hover' => '#D97706', 'hero_layout' => 'split', 'page_layout' => 'fifty-fifty', 'map_layout' => 'below-grid-large', 'map_density' => 'comfortable', 'map_provider' => 'openstreetmap', 'map_style' => 'osm-standard'],
            'professional_services' => ['accent' => '#0EA5E9', 'accent_hover' => '#0284C7', 'hero_layout' => 'minimal', 'page_layout' => 'fifty-fifty', 'map_layout' => 'three-column', 'map_density' => 'comfortable', 'map_provider' => 'openstreetmap', 'map_style' => 'carto-light'],
            'clubs_community' => ['accent' => '#16A34A', 'accent_hover' => '#15803D', 'hero_layout' => 'compact', 'page_layout' => 'single-column', 'map_layout' => 'below-list', 'map_density' => 'comfortable', 'map_provider' => 'openstreetmap', 'map_style' => 'osm-hot'],
            'venues_directory' => ['accent' => '#A855F7', 'accent_hover' => '#9333EA', 'hero_layout' => 'immersive', 'page_layout' => 'editorial', 'map_layout' => 'three-column', 'map_density' => 'comfortable', 'map_provider' => 'openstreetmap', 'map_style' => 'carto-dark'],
            'property_agents' => ['accent' => '#2563EB', 'accent_hover' => '#1D4ED8', 'hero_layout' => 'cinematic', 'page_layout' => 'modern', 'map_layout' => 'below-grid', 'map_density' => 'comfortable', 'map_provider' => 'openstreetmap', 'map_style' => 'osm-standard'],
            'health_wellness' => ['accent' => '#0F766E', 'accent_hover' => '#0D9488', 'hero_layout' => 'minimal', 'page_layout' => 'fifty-fifty', 'map_layout' => 'below-list', 'map_density' => 'comfortable', 'map_provider' => 'openstreetmap', 'map_style' => 'carto-light'],
            'restaurants_hospitality' => ['accent' => '#DC2626', 'accent_hover' => '#B91C1C', 'hero_layout' => 'spotlight', 'page_layout' => 'fifty-fifty', 'map_layout' => 'below-grid-large', 'map_density' => 'compact', 'map_provider' => 'openstreetmap', 'map_style' => 'carto-dark'],
            'education_training' => ['accent' => '#1D4ED8', 'accent_hover' => '#1E40AF', 'hero_layout' => 'minimal', 'page_layout' => 'fifty-fifty', 'map_layout' => 'below-grid', 'map_density' => 'comfortable', 'map_provider' => 'openstreetmap', 'map_style' => 'osm-standard'],
            'job_board' => ['accent' => '#0F172A', 'accent_hover' => '#1E293B', 'hero_layout' => 'compact', 'page_layout' => 'single-column', 'map_layout' => 'grid-no-map-large', 'map_density' => 'compact', 'map_provider' => 'openstreetmap', 'map_style' => 'carto-light'],
            'marketplace' => ['accent' => '#DB2777', 'accent_hover' => '#BE185D', 'hero_layout' => 'spotlight', 'page_layout' => 'fifty-fifty', 'map_layout' => 'grid-no-map-large', 'map_density' => 'compact', 'map_provider' => 'openstreetmap', 'map_style' => 'osm-standard'],
            'classifieds_marketplace' => ['accent' => '#DB2777', 'accent_hover' => '#BE185D', 'hero_layout' => 'spotlight', 'page_layout' => 'fifty-fifty', 'map_layout' => 'grid-no-map-large', 'map_density' => 'compact', 'map_provider' => 'openstreetmap', 'map_style' => 'osm-standard'],
            'franchises_multilocation' => ['accent' => '#7C3AED', 'accent_hover' => '#6D28D9', 'hero_layout' => 'cinematic', 'page_layout' => 'editorial', 'map_layout' => 'three-column', 'map_density' => 'comfortable', 'map_provider' => 'openstreetmap', 'map_style' => 'carto-light'],
            'nonprofits_charities' => ['accent' => '#E11D48', 'accent_hover' => '#BE123C', 'hero_layout' => 'minimal', 'page_layout' => 'fifty-fifty', 'map_layout' => 'below-list', 'map_density' => 'comfortable', 'map_provider' => 'openstreetmap', 'map_style' => 'osm-hot'],
            'startup_tech' => ['accent' => '#9333EA', 'accent_hover' => '#7E22CE', 'hero_layout' => 'immersive', 'page_layout' => 'editorial', 'map_layout' => 'below-grid-large', 'map_density' => 'comfortable', 'map_provider' => 'openstreetmap', 'map_style' => 'carto-dark'],
            'tourism_attractions' => ['accent' => '#EA580C', 'accent_hover' => '#C2410C', 'hero_layout' => 'cinematic', 'page_layout' => 'fifty-fifty', 'map_layout' => 'below-grid-large', 'map_density' => 'comfortable', 'map_provider' => 'openstreetmap', 'map_style' => 'topo'],
        ];
    }


    private function get_demo_profile_topics($profile_key) {
        $topics = [
            'events' => [
                ['title' => 'Global Robotics Summit', 'type' => 'Conference', 'mode' => 'In-person', 'price_range' => 'Premium', 'price_label' => '$299', 'tags' => 'Robotics|AI|Automation', 'official' => 'yes'],
                ['title' => 'Future of Aerospace', 'type' => 'Seminar', 'mode' => 'Hybrid', 'price_range' => 'Paid', 'price_label' => '$120', 'tags' => 'Aerospace|SpaceTech|Innovation', 'official' => 'yes'],
                ['title' => 'Green Energy Event Forum', 'type' => 'Forum', 'mode' => 'Online Stream Event', 'price_range' => 'Free', 'price_label' => 'Free', 'tags' => 'Energy|Sustainability|Climate', 'official' => 'no'],
                ['title' => 'Infrastructure Resilience Lab', 'type' => 'Workshop', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$85', 'tags' => 'Infrastructure|Civil|Risk', 'official' => 'no'],
            ],
            'events_single_organisation' => [
                ['title' => 'Flagship Annual Engineering Conference', 'type' => 'Conference', 'mode' => 'In-person', 'price_range' => 'Premium', 'price_label' => '$320', 'tags' => 'Flagship|Annual|Conference', 'official' => 'yes'],
                ['title' => 'Future Systems Leadership Summit', 'type' => 'Summit', 'mode' => 'Hybrid', 'price_range' => 'Paid', 'price_label' => '$180', 'tags' => 'Summit|Leadership|Innovation', 'official' => 'yes'],
                ['title' => 'Global Standards Congress', 'type' => 'Congress', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$210', 'tags' => 'Congress|Standards|Policy', 'official' => 'yes'],
                ['title' => 'Technology Expo & Trade Show', 'type' => 'Expo', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$95', 'tags' => 'Expo|Trade Show|Industry', 'official' => 'yes'],
                ['title' => 'Industry Awards Ceremony', 'type' => 'Award Ceremony', 'mode' => 'In-person', 'price_range' => 'Premium', 'price_label' => '$260', 'tags' => 'Awards|Recognition|Gala', 'official' => 'yes'],
                ['title' => 'Executive Networking Evening', 'type' => 'Networking Evening', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$60', 'tags' => 'Networking|Executive|Partners', 'official' => 'yes'],
                ['title' => 'Member Meetup Series', 'type' => 'Meetup', 'mode' => 'In-person', 'price_range' => 'Free', 'price_label' => 'Free', 'tags' => 'Members|Meetup|Recurring', 'official' => 'yes'],
                ['title' => 'Industry Mixer Social', 'type' => 'Mixer', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$45', 'tags' => 'Mixer|Industry|Community', 'official' => 'yes'],
                ['title' => 'Strategy Roundtable Forum', 'type' => 'Roundtable', 'mode' => 'Hybrid', 'price_range' => 'Paid', 'price_label' => '$80', 'tags' => 'Roundtable|Strategy|Leadership', 'official' => 'yes'],
                ['title' => 'Product Launch Showcase', 'type' => 'Product Launch', 'mode' => 'Hybrid', 'price_range' => 'Free', 'price_label' => 'Free', 'tags' => 'Product Launch|Showcase|Brand', 'official' => 'yes'],
                ['title' => 'Innovation Campus Open Day', 'type' => 'Open Day', 'mode' => 'In-person', 'price_range' => 'Free', 'price_label' => 'Free', 'tags' => 'Open Day|Campus|Public', 'official' => 'yes'],
                ['title' => 'Public Engineering Community Event', 'type' => 'Public Event', 'mode' => 'In-person', 'price_range' => 'Free', 'price_label' => 'Free', 'tags' => 'Public|Community|Outreach', 'official' => 'yes'],
                ['title' => 'Members-Only Strategy Session', 'type' => 'Members-Only Event', 'mode' => 'Hybrid', 'price_range' => 'Paid', 'price_label' => '$120', 'tags' => 'Members-Only|Strategy|Private', 'official' => 'yes'],
                ['title' => 'Quarterly Community Forum', 'type' => 'Forum', 'mode' => 'Online Stream Event', 'price_range' => 'Free', 'price_label' => 'Free', 'tags' => 'Forum|Community|Recurring', 'official' => 'yes'],
                ['title' => 'Regional Roadshow Series', 'type' => 'Roadshow', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$70', 'tags' => 'Roadshow|Regional|Tour', 'official' => 'yes'],
                ['title' => 'Brand Activation Live Experience', 'type' => 'Brand Activation', 'mode' => 'In-person', 'price_range' => 'Free', 'price_label' => 'Free', 'tags' => 'Brand|Activation|Experience', 'official' => 'yes'],
                ['title' => 'Engineering Scholarship Fundraiser', 'type' => 'Fundraiser', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$140', 'tags' => 'Fundraiser|Scholarship|Charity', 'official' => 'yes'],
                ['title' => 'VIP Partner Reception', 'type' => 'VIP Event', 'mode' => 'In-person', 'price_range' => 'Premium', 'price_label' => '$240', 'tags' => 'VIP|Partners|Reception', 'official' => 'yes'],
                ['title' => 'Startup Pitch Night', 'type' => 'Pitch Night', 'mode' => 'Hybrid', 'price_range' => 'Free', 'price_label' => 'Free', 'tags' => 'Pitch|Startup|Innovation', 'official' => 'yes'],
                ['title' => 'Hybrid Global Broadcast', 'type' => 'Global Broadcast', 'mode' => 'Hybrid', 'price_range' => 'Free', 'price_label' => 'Free', 'tags' => 'Hybrid|Broadcast|Global', 'official' => 'yes'],
                ['title' => 'Archive Spotlight: Annual Congress 2024', 'type' => 'Archive', 'mode' => 'Online Stream Event', 'price_range' => 'Free', 'price_label' => 'Free Replay', 'tags' => 'Archive|Past Event|On-demand', 'official' => 'yes'],
            ],
            'directory' => [
                ['title' => 'Downtown Artisan Bakery Listing', 'type' => 'Bakery', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'From $8', 'tags' => 'Bakery|Local|Family', 'official' => 'no'],
                ['title' => 'Neighborhood Bike Repair Shop', 'type' => 'Repair', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'Quote Required', 'tags' => 'Bikes|Repair|Community', 'official' => 'no'],
                ['title' => 'Business Legal Advisory Firm', 'type' => 'Legal', 'mode' => 'Hybrid', 'price_range' => 'Paid', 'price_label' => 'Consultation Fee', 'tags' => 'Legal|Advisory|Corporate', 'official' => 'no'],
                ['title' => 'Independent Bookstore Spotlight', 'type' => 'Retail', 'mode' => 'In-person', 'price_range' => 'Free', 'price_label' => 'Free Entry', 'tags' => 'Books|Retail|Culture', 'official' => 'no'],
            ],
            'local_businesses' => [
                ['title' => 'Downtown Artisan Bakery Listing', 'type' => 'Bakery', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'From $8', 'tags' => 'Bakery|Local|Family', 'official' => 'no'],
                ['title' => 'Neighborhood Bike Repair Shop', 'type' => 'Repair', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'Quote Required', 'tags' => 'Bikes|Repair|Community', 'official' => 'no'],
                ['title' => 'Independent Bookstore Spotlight', 'type' => 'Retail', 'mode' => 'In-person', 'price_range' => 'Free', 'price_label' => 'Free Entry', 'tags' => 'Books|Retail|Culture', 'official' => 'no'],
                ['title' => 'Local Florist & Gifts Directory', 'type' => 'Florist', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'From $25', 'tags' => 'Flowers|Gifts|Local', 'official' => 'no'],
            ],
            'trades_contractors' => [
                ['title' => 'Emergency Plumbing Contractor', 'type' => 'Plumbing', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'Quote Required', 'tags' => 'Plumbing|Emergency|Residential', 'official' => 'no'],
                ['title' => 'Electrical Safety Inspection Team', 'type' => 'Electrical', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'From $89', 'tags' => 'Electrical|Safety|Compliance', 'official' => 'no'],
                ['title' => 'Heating & Boiler Installers', 'type' => 'Heating', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'From $120', 'tags' => 'Heating|Boilers|Installers', 'official' => 'no'],
                ['title' => 'Trusted Builders & Renovation Crew', 'type' => 'Builders', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'Estimate Required', 'tags' => 'Builders|Renovation|Homes', 'official' => 'no'],
            ],
            'professional_services' => [
                ['title' => 'Business Legal Advisory Firm', 'type' => 'Legal', 'mode' => 'Hybrid', 'price_range' => 'Paid', 'price_label' => 'Consultation Fee', 'tags' => 'Legal|Advisory|Corporate', 'official' => 'no'],
                ['title' => 'Tax & Accounting Specialists', 'type' => 'Accounting', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'From $150', 'tags' => 'Tax|Accounting|SME', 'official' => 'no'],
                ['title' => 'Brand & Design Consultancy', 'type' => 'Design', 'mode' => 'Online Stream Event', 'price_range' => 'Paid', 'price_label' => 'Retainer', 'tags' => 'Branding|Design|Marketing', 'official' => 'no'],
                ['title' => 'IT Managed Service Provider', 'type' => 'IT Services', 'mode' => 'Hybrid', 'price_range' => 'Paid', 'price_label' => 'Monthly Plan', 'tags' => 'IT|Support|Managed Services', 'official' => 'no'],
            ],
            'clubs_community' => [
                ['title' => 'City Chess Club', 'type' => 'Club', 'mode' => 'In-person', 'price_range' => 'Free', 'price_label' => 'Membership', 'tags' => 'Chess|Community|Club', 'official' => 'no'],
                ['title' => 'Local Running Group', 'type' => 'Sports Club', 'mode' => 'In-person', 'price_range' => 'Free', 'price_label' => 'Free', 'tags' => 'Running|Fitness|Community', 'official' => 'no'],
                ['title' => 'Photography Meetup Collective', 'type' => 'Meetup', 'mode' => 'Hybrid', 'price_range' => 'Low Cost', 'price_label' => '$10', 'tags' => 'Photography|Meetup|Creative', 'official' => 'no'],
                ['title' => 'Neighborhood Parent Network', 'type' => 'Support Group', 'mode' => 'In-person', 'price_range' => 'Free', 'price_label' => 'Free', 'tags' => 'Parents|Support|Local', 'official' => 'no'],
            ],
            'venues_directory' => [
                ['title' => 'Riverside Wedding Venue', 'type' => 'Wedding Venue', 'mode' => 'In-person', 'price_range' => 'Premium', 'price_label' => 'From $2,000', 'tags' => 'Wedding|Venue|Riverside', 'official' => 'no'],
                ['title' => 'Executive Conference Hall', 'type' => 'Conference Venue', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'From $950/day', 'tags' => 'Conference|Corporate|Venue', 'official' => 'no'],
                ['title' => 'Urban Events Warehouse', 'type' => 'Event Space', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'From $650/day', 'tags' => 'Events|Warehouse|City', 'official' => 'no'],
                ['title' => 'Boutique Rooftop Venue', 'type' => 'Private Venue', 'mode' => 'In-person', 'price_range' => 'Premium', 'price_label' => 'From $1,250/day', 'tags' => 'Rooftop|Private|Hospitality', 'official' => 'no'],
            ],
            'property_agents' => [
                ['title' => 'Downtown Apartment Portfolio', 'type' => 'Residential', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'From $420k', 'tags' => 'Apartments|Residential|City', 'official' => 'no'],
                ['title' => 'Commercial Office Brokerage', 'type' => 'Commercial', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'Market Rate', 'tags' => 'Commercial|Offices|Brokerage', 'official' => 'no'],
                ['title' => 'Suburban Family Homes', 'type' => 'Estate Agent', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'From $560k', 'tags' => 'Homes|Family|Suburbs', 'official' => 'no'],
                ['title' => 'Luxury Penthouse Specialists', 'type' => 'Luxury', 'mode' => 'In-person', 'price_range' => 'Premium', 'price_label' => 'From $1.8m', 'tags' => 'Luxury|Penthouse|Prime', 'official' => 'no'],
            ],
            'health_wellness' => [
                ['title' => 'Holistic Wellness Studio', 'type' => 'Wellness', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'From $40', 'tags' => 'Wellness|Mindfulness|Local', 'official' => 'no'],
                ['title' => 'Sports Physiotherapy Clinic', 'type' => 'Physiotherapy', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'From $70', 'tags' => 'Physio|Rehab|Sports', 'official' => 'no'],
                ['title' => 'Nutrition Coaching Service', 'type' => 'Nutrition', 'mode' => 'Online Stream Event', 'price_range' => 'Paid', 'price_label' => 'From $55', 'tags' => 'Nutrition|Coaching|Health', 'official' => 'no'],
                ['title' => 'Community Mental Health Support', 'type' => 'Counselling', 'mode' => 'Hybrid', 'price_range' => 'Low Cost', 'price_label' => '$25', 'tags' => 'Mental Health|Support|Community', 'official' => 'no'],
            ],
            'restaurants_hospitality' => [
                ['title' => 'Rooftop Bistro & Cocktails', 'type' => 'Restaurant', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$$', 'tags' => 'Bistro|Rooftop|Dining', 'official' => 'no'],
                ['title' => 'Family Italian Kitchen', 'type' => 'Restaurant', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$$', 'tags' => 'Italian|Family|Local', 'official' => 'no'],
                ['title' => 'Boutique Hotel & Spa', 'type' => 'Hospitality', 'mode' => 'In-person', 'price_range' => 'Premium', 'price_label' => '$$$', 'tags' => 'Hotel|Spa|Luxury', 'official' => 'no'],
                ['title' => 'Specialty Coffee Roastery', 'type' => 'Cafe', 'mode' => 'In-person', 'price_range' => 'Low Cost', 'price_label' => '$', 'tags' => 'Coffee|Cafe|Roastery', 'official' => 'no'],
            ],
            'education_training' => [
                ['title' => 'Data Analytics Bootcamp Provider', 'type' => 'Bootcamp', 'mode' => 'Hybrid', 'price_range' => 'Paid', 'price_label' => 'From $399', 'tags' => 'Data|Bootcamp|Skills', 'official' => 'no'],
                ['title' => 'Language Learning Academy', 'type' => 'Academy', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'From $120', 'tags' => 'Language|Academy|Training', 'official' => 'no'],
                ['title' => 'Professional Certification Center', 'type' => 'Certification', 'mode' => 'Online Stream Event', 'price_range' => 'Paid', 'price_label' => 'From $250', 'tags' => 'Certification|Professional|Career', 'official' => 'no'],
                ['title' => 'Community Coding School', 'type' => 'Coding School', 'mode' => 'Hybrid', 'price_range' => 'Low Cost', 'price_label' => '$49', 'tags' => 'Coding|Community|Education', 'official' => 'yes'],
            ],
            'job_board' => [
                ['title' => 'Senior DevOps Engineer Opening', 'type' => 'Technology', 'mode' => 'Hybrid', 'price_range' => 'Paid', 'price_label' => '£85k', 'tags' => 'DevOps|Cloud|Hiring', 'official' => 'no'],
                ['title' => 'Marketing Manager Position', 'type' => 'Marketing', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '£58k', 'tags' => 'Marketing|Manager|Full-time', 'official' => 'no'],
                ['title' => 'Customer Success Specialist', 'type' => 'Customer Success', 'mode' => 'Online Stream Event', 'price_range' => 'Paid', 'price_label' => '£42k', 'tags' => 'Customer Success|SaaS|Remote', 'official' => 'no'],
                ['title' => 'Graduate Data Analyst Role', 'type' => 'Analytics', 'mode' => 'Hybrid', 'price_range' => 'Paid', 'price_label' => '£35k', 'tags' => 'Graduate|Data|Analytics', 'official' => 'yes'],
            ],
            'marketplace' => [
                ['title' => 'Vintage Camera Marketplace Listing', 'type' => 'Electronics', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$240', 'tags' => 'Camera|Vintage|Marketplace', 'official' => 'no'],
                ['title' => 'Home Office Furniture Bundle', 'type' => 'Furniture', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$320', 'tags' => 'Furniture|Office|Home', 'official' => 'no'],
                ['title' => 'Mountain Bike Classified Listing', 'type' => 'Sports', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$480', 'tags' => 'Bike|Sports|Preowned', 'official' => 'no'],
                ['title' => 'Local Handmade Crafts Seller', 'type' => 'Crafts', 'mode' => 'In-person', 'price_range' => 'Low Cost', 'price_label' => '$35', 'tags' => 'Crafts|Handmade|Seller', 'official' => 'no'],
            ],
            'classifieds_marketplace' => [
                ['title' => 'Vintage Camera Marketplace Listing', 'type' => 'Electronics', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$240', 'tags' => 'Camera|Vintage|Marketplace', 'official' => 'no'],
                ['title' => 'Home Office Furniture Bundle', 'type' => 'Furniture', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$320', 'tags' => 'Furniture|Office|Home', 'official' => 'no'],
                ['title' => 'Mountain Bike Classified Listing', 'type' => 'Sports', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$480', 'tags' => 'Bike|Sports|Preowned', 'official' => 'no'],
                ['title' => 'Local Handmade Crafts Seller', 'type' => 'Crafts', 'mode' => 'In-person', 'price_range' => 'Low Cost', 'price_label' => '$35', 'tags' => 'Crafts|Handmade|Seller', 'official' => 'no'],
            ],
            'franchises_multilocation' => [
                ['title' => 'BrewBox Downtown Franchise', 'type' => 'Food Franchise', 'mode' => 'In-person', 'price_range' => 'Premium', 'price_label' => '$500k+', 'tags' => 'Franchise|Food|Retail', 'official' => 'no'],
                ['title' => 'FitNation Multi-Site Gym Brand', 'type' => 'Fitness Franchise', 'mode' => 'In-person', 'price_range' => 'Premium', 'price_label' => '$380k+', 'tags' => 'Fitness|Franchise|Brand', 'official' => 'no'],
                ['title' => 'RapidFix Service Franchise', 'type' => 'Service Franchise', 'mode' => 'Hybrid', 'price_range' => 'Paid', 'price_label' => '$220k+', 'tags' => 'Service|Franchise|Growth', 'official' => 'no'],
                ['title' => 'EduLab Tutoring Branch Network', 'type' => 'Education Franchise', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$180k+', 'tags' => 'Education|Franchise|Tutoring', 'official' => 'no'],
            ],
            'nonprofits_charities' => [
                ['title' => 'Community Food Bank Organization', 'type' => 'Charity', 'mode' => 'In-person', 'price_range' => 'Free', 'price_label' => 'Donations', 'tags' => 'Charity|Food Bank|Community', 'official' => 'yes'],
                ['title' => 'Youth Mentorship Nonprofit', 'type' => 'Nonprofit', 'mode' => 'Hybrid', 'price_range' => 'Free', 'price_label' => 'Volunteer', 'tags' => 'Youth|Mentorship|Education', 'official' => 'yes'],
                ['title' => 'Environmental Action Group', 'type' => 'NGO', 'mode' => 'In-person', 'price_range' => 'Free', 'price_label' => 'Donations', 'tags' => 'Environment|NGO|Local', 'official' => 'no'],
                ['title' => 'Community Health Outreach Charity', 'type' => 'Charity', 'mode' => 'In-person', 'price_range' => 'Free', 'price_label' => 'Donations', 'tags' => 'Health|Charity|Outreach', 'official' => 'no'],
            ],
            'startup_tech' => [
                ['title' => 'AI Workflow Startup Profile', 'type' => 'SaaS', 'mode' => 'Hybrid', 'price_range' => 'Premium', 'price_label' => 'Series A', 'tags' => 'AI|Startup|SaaS', 'official' => 'no'],
                ['title' => 'Climate Tech Venture Studio', 'type' => 'Climate Tech', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => 'Seed Stage', 'tags' => 'Climate|Startup|Venture', 'official' => 'no'],
                ['title' => 'Developer Tooling Company', 'type' => 'Developer Tools', 'mode' => 'Online Stream Event', 'price_range' => 'Paid', 'price_label' => 'Growth Stage', 'tags' => 'Developers|APIs|Tooling', 'official' => 'no'],
                ['title' => 'Fintech Platform Builder', 'type' => 'Fintech', 'mode' => 'Hybrid', 'price_range' => 'Premium', 'price_label' => 'Series B', 'tags' => 'Fintech|Payments|Scale-up', 'official' => 'yes'],
            ],
            'tourism_attractions' => [
                ['title' => 'Historic Castle Guided Tour', 'type' => 'Attraction', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$25', 'tags' => 'Tourism|Castle|Guided', 'official' => 'no'],
                ['title' => 'City Museum Experience', 'type' => 'Museum', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$18', 'tags' => 'Museum|Culture|City', 'official' => 'yes'],
                ['title' => 'Adventure Park Outdoor Pass', 'type' => 'Adventure', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$60', 'tags' => 'Adventure|Outdoor|Family', 'official' => 'no'],
                ['title' => 'Scenic Boat Cruise Attraction', 'type' => 'Tour', 'mode' => 'In-person', 'price_range' => 'Paid', 'price_label' => '$35', 'tags' => 'Cruise|Scenic|Tour', 'official' => 'no'],
            ],
        ];

        $profile_key = sanitize_key((string) $profile_key);
        if (!isset($topics[$profile_key]) || !is_array($topics[$profile_key])) {
            return (array) ($topics['events'] ?? []);
        }
        return (array) $topics[$profile_key];
    }

    private function get_demo_photo_urls_from_folder($relative_folder) {
        $relative_folder = trim((string) $relative_folder, "/\\");
        if ($relative_folder === '') {
            return [];
        }

        $base_dir = trailingslashit((string) plugin_dir_path(EEP_PLUGIN_FILE)) . $relative_folder;
        if (!is_dir($base_dir) || !is_readable($base_dir)) {
            return [];
        }

        $patterns = [
            $base_dir . '/*.jpg',
            $base_dir . '/*.jpeg',
            $base_dir . '/*.png',
            $base_dir . '/*.webp',
            $base_dir . '/*.gif',
            $base_dir . '/*.avif',
            $base_dir . '/*.JPG',
            $base_dir . '/*.JPEG',
            $base_dir . '/*.PNG',
            $base_dir . '/*.WEBP',
            $base_dir . '/*.GIF',
            $base_dir . '/*.AVIF',
        ];

        $files = [];
        foreach ($patterns as $pattern) {
            $matched = glob($pattern);
            if (!is_array($matched) || empty($matched)) {
                continue;
            }
            $files = array_merge($files, $matched);
        }

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

        $files = array_values(array_unique(array_filter($files, 'is_file')));
        natcasesort($files);
        $files = array_values($files);

        $base_url = trailingslashit((string) plugins_url($relative_folder, EEP_PLUGIN_FILE));
        $urls = [];
        foreach ($files as $file) {
            $filename = basename((string) $file);
            if ($filename === '' || $filename[0] === '.') {
                continue;
            }
            $urls[] = esc_url_raw($base_url . rawurlencode($filename));
        }

        return array_values(array_unique(array_filter($urls)));
    }

    private function get_demo_corporate_photo_urls() {
        $photos = $this->get_demo_photo_urls_from_folder('Photos/Corporate');
        if (!empty($photos)) {
            return $photos;
        }
        // Backward compatibility for older demo media folder structures.
        return $this->get_demo_photo_urls_from_folder('Photos/DemoContent');
    }

    private function get_demo_logo_photo_urls() {
        $logos = $this->get_demo_photo_urls_from_folder('Photos/Corporate/logo');
        if (!empty($logos)) {
            return $logos;
        }
        // Backward compatibility for older demo media folder structures.
        return $this->get_demo_photo_urls_from_folder('Photos/DemoContent/logo');
    }

    private function pick_demo_logo_url() {
        $logo_urls = $this->get_demo_logo_photo_urls();
        if (!empty($logo_urls)) {
            return esc_url_raw((string) $logo_urls[wp_rand(0, count($logo_urls) - 1)]);
        }
        return '';
    }

    private function get_demo_headshot_photo_urls($gender = 'man') {
        $gender = sanitize_key((string) $gender);
        if (!in_array($gender, ['man', 'woman'], true)) {
            $gender = 'man';
        }
        return $this->get_demo_photo_urls_from_folder('Photos/headshots/' . $gender);
    }

    private function pick_demo_headshot_url($gender = 'man', array $fallback_urls = []) {
        $headshots = $this->get_demo_headshot_photo_urls($gender);
        if (!empty($headshots)) {
            return esc_url_raw((string) $headshots[wp_rand(0, count($headshots) - 1)]);
        }

        $fallback_urls = array_values(array_filter(array_map('esc_url_raw', $fallback_urls)));
        if (!empty($fallback_urls)) {
            return esc_url_raw((string) $fallback_urls[wp_rand(0, count($fallback_urls) - 1)]);
        }

        return '';
    }


    private function get_demo_profile_media_pack($profile_key) {
        $keyword_map = [
            'events' => ['event conference', 'robotics lab', 'aerospace launch', 'technology seminar', 'innovation summit', 'professional meetup', 'auditorium keynote', 'event team'],
            'directory' => ['local shop storefront', 'small business owner', 'agency workspace', 'service provider office', 'community listing', 'bookstore aisle', 'coffee counter', 'independent retailer'],
            'local_businesses' => ['local shop storefront', 'small business owner', 'bakery display', 'bookstore aisle', 'coffee counter', 'neighborhood store', 'florist shop', 'independent retailer'],
            'trades_contractors' => ['construction worker', 'electrician tools', 'plumber service', 'home renovation', 'contractor team', 'boiler install', 'building repair', 'trade specialist'],
            'professional_services' => ['consulting office', 'legal advisor desk', 'accounting team', 'agency workspace', 'business meeting', 'service professionals', 'corporate discussion', 'strategy session'],
            'clubs_community' => ['community club', 'local meetup', 'group discussion', 'sports club', 'chess match', 'community volunteers', 'neighborhood gathering', 'creative workshop'],
            'venues_directory' => ['wedding venue hall', 'conference venue', 'event space interior', 'banquet hall', 'rooftop venue', 'venue lights', 'ballroom setup', 'conference stage'],
            'property_agents' => ['real estate office', 'modern apartment', 'property exterior', 'home interior', 'estate agent', 'for sale house', 'city apartment', 'residential property'],
            'health_wellness' => ['wellness clinic', 'physiotherapy session', 'nutrition coach', 'mental health support', 'yoga studio', 'health consultation', 'wellness center', 'therapy room'],
            'restaurants_hospitality' => ['restaurant interior', 'chef kitchen', 'hotel lobby', 'cafe counter', 'fine dining', 'hospitality service', 'rooftop dining', 'coffee shop'],
            'education_training' => ['classroom training', 'students learning', 'coding bootcamp', 'lecture hall', 'workshop class', 'education campus', 'teacher presentation', 'skills training'],
            'job_board' => ['job interview', 'recruitment fair', 'office team', 'career hiring', 'professional resume', 'workplace meeting', 'job seeker', 'business hiring'],
            'marketplace' => ['marketplace products', 'second hand goods', 'classified listing', 'local market stall', 'vintage camera', 'furniture sale', 'bike resale', 'online seller'],
            'classifieds_marketplace' => ['marketplace products', 'second hand goods', 'classified listing', 'local market stall', 'vintage camera', 'furniture sale', 'bike resale', 'online seller'],
            'franchises_multilocation' => ['franchise storefront', 'brand locations', 'retail chain', 'business expansion', 'multi location business', 'franchise owner', 'chain cafe', 'brand headquarters'],
            'nonprofits_charities' => ['charity volunteers', 'nonprofit team', 'community donation', 'food bank support', 'outreach program', 'charity event', 'volunteer group', 'social impact'],
            'startup_tech' => ['startup office', 'tech founders', 'developer team', 'product launch', 'ai startup', 'venture capital pitch', 'innovation workspace', 'software company'],
            'tourism_attractions' => ['tourist attraction', 'historic castle', 'museum gallery', 'city landmark', 'scenic view', 'boat cruise', 'travel destination', 'adventure park'],
        ];

        $profile_key = sanitize_key((string) $profile_key);
        $keywords = isset($keyword_map[$profile_key]) && is_array($keyword_map[$profile_key]) ? $keyword_map[$profile_key] : (array) ($keyword_map['events'] ?? []);
        if (empty($keywords)) {
            $keywords = ['professional listing'];
        }

        $cover_images = $this->get_demo_corporate_photo_urls();
        $gallery_images = $cover_images;

        if (empty($cover_images) || empty($gallery_images)) {
            $cover_images = [];
            $gallery_images = [];
            foreach ($keywords as $index => $keyword) {
                $keyword = trim((string) $keyword);
                if ($keyword === '') {
                    continue;
                }
                $seed = eep_sanitize_title($profile_key . '-' . $keyword . '-' . (string) $index);
                if ($seed === '') {
                    $seed = eep_sanitize_title($profile_key . '-listing-' . (string) $index);
                }
                $cover_images[] = 'https://picsum.photos/seed/' . rawurlencode($seed . '-cover') . '/1600/900';
                $gallery_images[] = 'https://picsum.photos/seed/' . rawurlencode($seed . '-gallery') . '/1200/900';
            }
        }

        $videos_by_profile = [
            'events' => ['https://www.youtube.com/watch?v=ScMzIvxBSi4', 'https://www.youtube.com/watch?v=LXb3EKWsInQ', 'https://www.youtube.com/watch?v=EngW7tLk6R8'],
            'directory' => ['https://www.youtube.com/watch?v=ysz5S6PUM-U', 'https://www.youtube.com/watch?v=oUFJJNQGwhk', 'https://www.youtube.com/watch?v=aqz-KE-bpKQ'],
            'local_businesses' => ['https://www.youtube.com/watch?v=ysz5S6PUM-U', 'https://www.youtube.com/watch?v=oUFJJNQGwhk', 'https://www.youtube.com/watch?v=aqz-KE-bpKQ'],
            'trades_contractors' => ['https://www.youtube.com/watch?v=aqz-KE-bpKQ', 'https://www.youtube.com/watch?v=ScMzIvxBSi4', 'https://www.youtube.com/watch?v=oUFJJNQGwhk'],
            'professional_services' => ['https://www.youtube.com/watch?v=LXb3EKWsInQ', 'https://www.youtube.com/watch?v=ScMzIvxBSi4', 'https://www.youtube.com/watch?v=ysz5S6PUM-U'],
            'clubs_community' => ['https://www.youtube.com/watch?v=EngW7tLk6R8', 'https://www.youtube.com/watch?v=aqz-KE-bpKQ', 'https://www.youtube.com/watch?v=ScMzIvxBSi4'],
            'venues_directory' => ['https://www.youtube.com/watch?v=oUFJJNQGwhk', 'https://www.youtube.com/watch?v=LXb3EKWsInQ', 'https://www.youtube.com/watch?v=ScMzIvxBSi4'],
            'property_agents' => ['https://www.youtube.com/watch?v=ysz5S6PUM-U', 'https://www.youtube.com/watch?v=EngW7tLk6R8', 'https://www.youtube.com/watch?v=LXb3EKWsInQ'],
            'health_wellness' => ['https://www.youtube.com/watch?v=ScMzIvxBSi4', 'https://www.youtube.com/watch?v=aqz-KE-bpKQ', 'https://www.youtube.com/watch?v=EngW7tLk6R8'],
            'restaurants_hospitality' => ['https://www.youtube.com/watch?v=LXb3EKWsInQ', 'https://www.youtube.com/watch?v=ysz5S6PUM-U', 'https://www.youtube.com/watch?v=oUFJJNQGwhk'],
            'education_training' => ['https://www.youtube.com/watch?v=EngW7tLk6R8', 'https://www.youtube.com/watch?v=ScMzIvxBSi4', 'https://www.youtube.com/watch?v=aqz-KE-bpKQ'],
            'job_board' => ['https://www.youtube.com/watch?v=ysz5S6PUM-U', 'https://www.youtube.com/watch?v=LXb3EKWsInQ', 'https://www.youtube.com/watch?v=ScMzIvxBSi4'],
            'marketplace' => ['https://www.youtube.com/watch?v=oUFJJNQGwhk', 'https://www.youtube.com/watch?v=aqz-KE-bpKQ', 'https://www.youtube.com/watch?v=EngW7tLk6R8'],
            'classifieds_marketplace' => ['https://www.youtube.com/watch?v=oUFJJNQGwhk', 'https://www.youtube.com/watch?v=aqz-KE-bpKQ', 'https://www.youtube.com/watch?v=EngW7tLk6R8'],
            'franchises_multilocation' => ['https://www.youtube.com/watch?v=LXb3EKWsInQ', 'https://www.youtube.com/watch?v=ScMzIvxBSi4', 'https://www.youtube.com/watch?v=oUFJJNQGwhk'],
            'nonprofits_charities' => ['https://www.youtube.com/watch?v=EngW7tLk6R8', 'https://www.youtube.com/watch?v=aqz-KE-bpKQ', 'https://www.youtube.com/watch?v=ysz5S6PUM-U'],
            'startup_tech' => ['https://www.youtube.com/watch?v=ScMzIvxBSi4', 'https://www.youtube.com/watch?v=LXb3EKWsInQ', 'https://www.youtube.com/watch?v=oUFJJNQGwhk'],
            'tourism_attractions' => ['https://www.youtube.com/watch?v=aqz-KE-bpKQ', 'https://www.youtube.com/watch?v=EngW7tLk6R8', 'https://www.youtube.com/watch?v=ysz5S6PUM-U'],
        ];
        $video_urls = isset($videos_by_profile[$profile_key]) && is_array($videos_by_profile[$profile_key])
            ? (array) $videos_by_profile[$profile_key]
            : (array) ($videos_by_profile['events'] ?? []);

        $supporting_docs_by_profile = [
            'events' => [
                'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
                'https://calibre-ebook.com/downloads/demos/demo.docx',
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/10/file-example_PDF_1MB.pdf',
            ],
            'directory' => [
                'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/02/file-sample_100kB.doc',
                'https://calibre-ebook.com/downloads/demos/demo.docx',
            ],
            'local_businesses' => [
                'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/02/file-sample_100kB.doc',
                'https://calibre-ebook.com/downloads/demos/demo.docx',
            ],
            'trades_contractors' => [
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/10/file-example_PDF_1MB.pdf',
                'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/02/file-sample_100kB.doc',
            ],
            'professional_services' => [
                'https://calibre-ebook.com/downloads/demos/demo.docx',
                'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/10/file-example_PDF_1MB.pdf',
            ],
            'clubs_community' => [
                'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
                'https://calibre-ebook.com/downloads/demos/demo.docx',
            ],
            'venues_directory' => [
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/10/file-example_PDF_1MB.pdf',
                'https://calibre-ebook.com/downloads/demos/demo.docx',
            ],
            'property_agents' => [
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/10/file-example_PDF_1MB.pdf',
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/02/file-sample_100kB.doc',
            ],
            'health_wellness' => [
                'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
                'https://calibre-ebook.com/downloads/demos/demo.docx',
            ],
            'restaurants_hospitality' => [
                'https://calibre-ebook.com/downloads/demos/demo.docx',
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/10/file-example_PDF_1MB.pdf',
            ],
            'education_training' => [
                'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
                'https://calibre-ebook.com/downloads/demos/demo.docx',
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/02/file-sample_100kB.doc',
            ],
            'job_board' => [
                'https://calibre-ebook.com/downloads/demos/demo.docx',
                'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
            ],
            'marketplace' => [
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/02/file-sample_100kB.doc',
                'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
            ],
            'classifieds_marketplace' => [
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/02/file-sample_100kB.doc',
                'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
            ],
            'franchises_multilocation' => [
                'https://calibre-ebook.com/downloads/demos/demo.docx',
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/10/file-example_PDF_1MB.pdf',
            ],
            'nonprofits_charities' => [
                'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
                'https://calibre-ebook.com/downloads/demos/demo.docx',
            ],
            'startup_tech' => [
                'https://calibre-ebook.com/downloads/demos/demo.docx',
                'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/10/file-example_PDF_1MB.pdf',
            ],
            'tourism_attractions' => [
                'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
                'https://file-examples.com/storage/fe5b9b6f21e0f0e5d6f2f8b/2017/02/file-sample_100kB.doc',
            ],
        ];
        $supporting_docs = isset($supporting_docs_by_profile[$profile_key]) && is_array($supporting_docs_by_profile[$profile_key])
            ? (array) $supporting_docs_by_profile[$profile_key]
            : (array) ($supporting_docs_by_profile['events'] ?? []);

        return [
            'cover_images' => array_values(array_unique(array_filter($cover_images))),
            'gallery_images' => array_values(array_unique(array_filter($gallery_images))),
            'video_urls' => array_values(array_unique(array_filter($video_urls))),
            'supporting_docs' => $supporting_docs,
        ];
    }

    private function get_demo_profile_config($profile_key) {
        $catalog = $this->get_demo_profile_catalog();
        $profile_key = sanitize_key((string) $profile_key);
        if ($profile_key === '' || !isset($catalog[$profile_key])) {
            $profile_key = $this->get_demo_profile_key_for_platform_mode();
        }
        if ($profile_key === '' || !isset($catalog[$profile_key])) {
            $profile_key = 'events';
        }
        if (!isset($catalog[$profile_key])) {
            $first_key = array_key_first($catalog);
            $profile_key = is_string($first_key) ? $first_key : 'events';
        }
        $config = (array) ($catalog[$profile_key] ?? []);
        $config['key'] = $profile_key;
        $config['label'] = sanitize_text_field((string) ($config['label'] ?? 'Demo Profile'));
        $config['description'] = sanitize_text_field((string) ($config['description'] ?? ''));
        return $config;
    }


    private function apply_demo_profile_settings(array $profile_config) {
        $accent = $this->sanitize_design_color((string) ($profile_config['accent'] ?? '#FF5E14'), '#FF5E14');
        $accent_hover = $this->sanitize_design_color((string) ($profile_config['accent_hover'] ?? '#E04F0D'), '#E04F0D');
        $hero_layout = sanitize_key((string) ($profile_config['hero_layout'] ?? 'cinematic'));
        if (!in_array($hero_layout, ['cinematic', 'compact', 'split', 'minimal', 'immersive', 'spotlight'], true)) {
            $hero_layout = 'cinematic';
        }
        $page_layout = sanitize_key((string) ($profile_config['page_layout'] ?? 'modern'));
        if (in_array($page_layout, ['balanced', 'compact-aside', 'cards-heavy'], true)) {
            $page_layout = 'fifty-fifty';
        }
        if (!in_array($page_layout, ['modern', 'single-column', 'editorial', 'fifty-fifty', 'left-sidebar', 'zig-zag', 'full3', 'zen'], true)) {
            $page_layout = 'modern';
        }
        $map_layout = sanitize_key((string) ($profile_config['map_layout'] ?? 'three-column'));
        if (!in_array($map_layout, ['three-column', 'below-list', 'below-grid', 'below-grid-large', 'grid-no-map', 'grid-no-map-large'], true)) {
            $map_layout = 'three-column';
        }
        $map_density = sanitize_key((string) ($profile_config['map_density'] ?? 'comfortable'));
        if (!in_array($map_density, ['compact', 'comfortable'], true)) {
            $map_density = 'comfortable';
        }
        $map_provider = sanitize_key((string) ($profile_config['map_provider'] ?? 'openstreetmap'));
        if (!in_array($map_provider, ['openstreetmap', 'mapbox', 'google'], true)) {
            $map_provider = 'openstreetmap';
        }
        $map_style = sanitize_key((string) ($profile_config['map_style'] ?? 'osm-standard'));
        if (!in_array($map_style, ['osm-standard', 'osm-hot', 'carto-light', 'carto-dark', 'topo', 'mapbox-streets', 'mapbox-outdoors', 'mapbox-satellite', 'google-roadmap', 'google-satellite', 'custom'], true)) {
            $map_style = 'osm-standard';
        }
        $hero_kicker = sanitize_text_field((string) ($profile_config['hero_kicker'] ?? ''));
        if ($hero_kicker === '') {
            $hero_kicker = 'NexDirectory';
        }

        update_option('eep_style_accent_color', $accent, false);
        update_option('eep_style_accent_hover_color', $accent_hover, false);
        update_option('eep_event_hero_layout', $hero_layout, false);
        update_option('eep_event_page_layout', $page_layout, false);
        update_option('eep_map_layout_default', $map_layout, false);
        update_option('eep_map_view_template', 'default', false);
        update_option('eep_map_results_density', $map_density, false);
        update_option('eep_map_provider', $map_provider, false);
        update_option('eep_map_style', $map_style, false);
        update_option('eep_event_hero_kicker_text', $hero_kicker, false);
        $demo_logo_url = $this->pick_demo_logo_url();
        if ($demo_logo_url !== '') {
            update_option('eep_frontend_sticky_nav_logo_url', $demo_logo_url, false);
            update_option('eep_frontend_sticky_nav_logo_link_url', esc_url_raw(home_url('/')), false);
        }
        $site_profile_key = sanitize_key((string) ($profile_config['site_profile'] ?? $profile_config['key'] ?? 'events'));
        if ($site_profile_key !== '') {
            update_option('eep_site_profile', $site_profile_key, false);
        }

        $topics = isset($profile_config['topics']) && is_array($profile_config['topics']) ? $profile_config['topics'] : [];
        foreach ($topics 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 !== '' && !term_exists($type, self::TAX_TYPE)) {
                wp_insert_term($type, self::TAX_TYPE);
            }
            if ($mode !== '' && !term_exists($mode, self::TAX_MODE)) {
                wp_insert_term($mode, self::TAX_MODE);
            }
            if ($price !== '' && !term_exists($price, self::TAX_PRICE)) {
                wp_insert_term($price, self::TAX_PRICE);
            }
            $tags = $this->parse_delimited_values((string) ($topic['tags'] ?? ''));
            foreach ($tags as $tag) {
                $tag = sanitize_text_field((string) $tag);
                if ($tag === '') {
                    continue;
                }
                if (!term_exists($tag, self::TAX_TAG)) {
                    wp_insert_term($tag, self::TAX_TAG);
                }
            }
        }
    }


    private function build_demo_gallery_urls_for_row(array $gallery_images, $cover_image, $profile_key, $row_index) {
        $gallery_images = array_values(array_unique(array_filter(array_map('esc_url_raw', $gallery_images), static function ($url) {
            return trim((string) $url) !== '';
        })));
        $cover_image = esc_url_raw((string) $cover_image);

        if ($cover_image !== '') {
            $gallery_images = array_values(array_filter($gallery_images, static function ($url) use ($cover_image) {
                return (string) $url !== $cover_image;
            }));
        }

        $target_count = wp_rand(3, 6);
        $selected = [];

        if (!empty($gallery_images)) {
            $pool_size = count($gallery_images);
            $start_offset = $pool_size > 0 ? (((int) $row_index) % $pool_size) : 0;
            for ($offset = 0; $offset < $pool_size && count($selected) < $target_count; $offset++) {
                $candidate = (string) ($gallery_images[($start_offset + $offset) % $pool_size] ?? '');
                if ($candidate !== '' && !in_array($candidate, $selected, true)) {
                    $selected[] = $candidate;
                }
            }
        }

        $slot = 0;
        while (count($selected) < $target_count && $slot < 24) {
            $seed = eep_sanitize_title((string) $profile_key . '-demo-gallery-' . (string) $row_index . '-' . (string) $slot);
            if ($seed === '') {
                $seed = 'nexdirectory-demo-gallery-' . (string) $row_index . '-' . (string) $slot;
            }
            $fallback_url = esc_url_raw('https://picsum.photos/seed/' . rawurlencode($seed) . '/1200/900');
            if ($fallback_url !== '' && $fallback_url !== $cover_image && !in_array($fallback_url, $selected, true)) {
                $selected[] = $fallback_url;
            }
            $slot++;
        }

        return array_values(array_slice($selected, 0, 6));
    }


    private function build_demo_import_rows($count = 50, $profile_key = 'events') {
        $count = max(1, min(200, absint((int) $count)));
        $profile_key = sanitize_key((string) $profile_key);
        $profile_config = $this->get_demo_profile_config($profile_key);
        $topic_seeds = !empty($profile_config['topics']) && is_array($profile_config['topics'])
            ? array_values((array) $profile_config['topics'])
            : $this->get_demo_profile_topics($profile_key);
        if (empty($topic_seeds)) {
            $topic_seeds = $this->get_demo_profile_topics('events');
        }

        $ui_labels = method_exists($this, 'get_site_profile_ui_labels')
            ? (array) $this->get_site_profile_ui_labels((string) ($profile_config['site_profile'] ?? $profile_key))
            : [];
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $singular_lower = sanitize_text_field((string) ($ui_labels['singular_lower'] ?? strtolower($singular_label)));
        $plural_label = sanitize_text_field((string) ($ui_labels['plural'] ?? __('Events', 'nexdirectory')));
        $is_events_profile = (string) ($ui_labels['is_events_profile'] ?? '1') === '1';
        $is_job_profile = (string) ($ui_labels['is_job_profile'] ?? '0') === '1';
        $is_single_org_demo = $profile_key === 'events_single_organisation';

        $brand_name = sanitize_text_field((string) $this->get_white_label_brand_name());
        if ($brand_name === '') {
            $brand_name = sanitize_text_field((string) get_bloginfo('name'));
        }
        if ($brand_name === '') {
            $brand_name = 'NexDirectory';
        }
        $brand_email_local = eep_sanitize_title($brand_name);
        if ($brand_email_local === '') {
            $brand_email_local = 'company';
        }
        $single_org_contact_email = $brand_email_local . '.events@example.com';
        $profile_label = sanitize_text_field((string) ($profile_config['label'] ?? 'Demo Profile'));
        $media_pack = isset($profile_config['media_pack']) && is_array($profile_config['media_pack'])
            ? (array) $profile_config['media_pack']
            : $this->get_demo_profile_media_pack($profile_key);
        $location_seeds = [
            ['city' => 'London', 'country' => 'United Kingdom', 'timezone' => 'Europe/London', 'lat' => '51.5074', 'lng' => '-0.1278', 'venue' => 'ExCeL London', 'address' => 'Royal Victoria Dock, 1 Western Gateway'],
            ['city' => 'New York', 'country' => 'United States', 'timezone' => 'America/New_York', 'lat' => '40.7128', 'lng' => '-74.0060', 'venue' => 'Javits Center', 'address' => '429 11th Ave'],
            ['city' => 'Toronto', 'country' => 'Canada', 'timezone' => 'America/Toronto', 'lat' => '43.6532', 'lng' => '-79.3832', 'venue' => 'Metro Toronto Convention Centre', 'address' => '255 Front St W'],
            ['city' => 'Berlin', 'country' => 'Germany', 'timezone' => 'Europe/Berlin', 'lat' => '52.5200', 'lng' => '13.4050', 'venue' => 'CityCube Berlin', 'address' => 'Messedamm 26'],
            ['city' => 'Paris', 'country' => 'France', 'timezone' => 'Europe/Paris', 'lat' => '48.8566', 'lng' => '2.3522', 'venue' => 'Palais des Congres', 'address' => '2 Place de la Porte Maillot'],
            ['city' => 'Singapore', 'country' => 'Singapore', 'timezone' => 'Asia/Singapore', 'lat' => '1.3521', 'lng' => '103.8198', 'venue' => 'Suntec Convention Centre', 'address' => '1 Raffles Blvd'],
            ['city' => 'Sydney', 'country' => 'Australia', 'timezone' => 'Australia/Sydney', 'lat' => '-33.8688', 'lng' => '151.2093', 'venue' => 'ICC Sydney', 'address' => '14 Darling Dr'],
            ['city' => 'Dubai', 'country' => 'United Arab Emirates', 'timezone' => 'Asia/Dubai', 'lat' => '25.2048', 'lng' => '55.2708', 'venue' => 'Dubai World Trade Centre', 'address' => 'Sheikh Zayed Rd'],
            ['city' => 'Tokyo', 'country' => 'Japan', 'timezone' => 'Asia/Tokyo', 'lat' => '35.6762', 'lng' => '139.6503', 'venue' => 'Tokyo Big Sight', 'address' => '3-11-1 Ariake'],
            ['city' => 'Sao Paulo', 'country' => 'Brazil', 'timezone' => 'America/Sao_Paulo', 'lat' => '-23.5505', 'lng' => '-46.6333', 'venue' => 'Expo Center Norte', 'address' => 'Rua Jose Bernardo Pinto, 333'],
        ];
        $cover_images = array_values(array_filter(array_map('esc_url_raw', (array) ($media_pack['cover_images'] ?? []))));
        $gallery_images = array_values(array_filter(array_map('esc_url_raw', (array) ($media_pack['gallery_images'] ?? []))));
        $video_urls = array_values(array_filter(array_map('esc_url_raw', (array) ($media_pack['video_urls'] ?? []))));
        $supporting_docs = array_values(array_filter(array_map('esc_url_raw', (array) ($media_pack['supporting_docs'] ?? []))));

        $corporate_images = $this->get_demo_corporate_photo_urls();
        if (!empty($corporate_images)) {
            $cover_images = $corporate_images;
            $gallery_images = $corporate_images;
        }

        if (empty($cover_images)) {
            $cover_images = ['https://picsum.photos/seed/nexdirectory-demo-cover/1600/900'];
        }
        if (empty($gallery_images)) {
            $gallery_images = ['https://picsum.photos/seed/nexdirectory-demo-gallery/1200/900'];
        }
        if (empty($video_urls)) {
            $video_urls = ['https://www.youtube.com/watch?v=ScMzIvxBSi4'];
        }
        if (empty($supporting_docs)) {
            $supporting_docs = ['https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf'];
        }
        $logo_images = array_values(array_filter(array_map('esc_url_raw', $this->get_demo_logo_photo_urls())));
        if (empty($logo_images)) {
            $logo_images = array_values(array_filter(array_map('esc_url_raw', $cover_images)));
        }
        $webapp_accent = $this->sanitize_design_color((string) ($profile_config['accent'] ?? '#4F46E5'), '#4F46E5');
        $webapp_loading_background = '#FFFFFF';
        $speaker_professions = ['Chief Innovation Officer', 'Research Director', 'Principal Engineer', 'Product Lead', 'Solutions Architect'];
        $session_notes_pool = [
            'Arrive 15 minutes early for badge scanning and seating.',
            'Interactive segment included. Bring your questions.',
            'Reference docs and slides are available in the attachment list.',
            'Networking opportunity follows this session.',
            'Save this item to your custom itinerary if attending.',
        ];

        $rows = [];
        $base_start = strtotime('+7 days 09:00');
        if (!$base_start) {
            $base_start = time() + (7 * DAY_IN_SECONDS);
        }

        $male_first_names = ['Liam', 'Noah', 'Ethan', 'Mason', 'James', 'Daniel', 'Aiden', 'Oliver', 'Henry', 'Lucas'];
        $female_first_names = ['Emma', 'Olivia', 'Ava', 'Sophia', 'Mia', 'Isabella', 'Amelia', 'Charlotte', 'Harper', 'Evelyn'];
        $last_names = ['Walker', 'Carter', 'Mitchell', 'Bennett', 'Reed', 'Coleman', 'Murphy', 'Morgan', 'Hayes', 'Foster'];

        for ($i = 0; $i < $count; $i++) {
            $topic = $topic_seeds[$i % count($topic_seeds)];
            $location = $location_seeds[$i % count($location_seeds)];
            $topic_title = sanitize_text_field((string) ($topic['title'] ?? 'Community Event'));
            $topic_type = sanitize_text_field((string) ($topic['type'] ?? 'Conference'));
            $topic_mode = sanitize_text_field((string) ($topic['mode'] ?? 'In-person'));
            $topic_price_range = sanitize_text_field((string) ($topic['price_range'] ?? 'Free'));
            $topic_price_label = sanitize_text_field((string) ($topic['price_label'] ?? 'Free'));
            $topic_tags = sanitize_text_field((string) ($topic['tags'] ?? 'Community'));
            $topic_official = $this->normalize_official_event_value($topic['official'] ?? '0');
            $cover_image = $cover_images[$i % count($cover_images)];
            $gallery_urls_for_row = $this->build_demo_gallery_urls_for_row($gallery_images, $cover_image, $profile_key, $i);
            $video_1 = $video_urls[$i % count($video_urls)];
            $video_2 = $video_urls[($i + 2) % count($video_urls)];
            $doc_1 = $supporting_docs[$i % count($supporting_docs)];
            $doc_2 = $supporting_docs[($i + 1) % count($supporting_docs)];

            $start_ts = (int) strtotime('+' . (string) ($i * 2) . ' days', $base_start);
            if ($is_events_profile && $is_single_org_demo) {
                $is_archived_row = stripos($topic_title, 'archive') !== false || ($i % 6 === 0);
                if ($is_archived_row) {
                    $past_offset_days = 30 + (($i * 11) % 330);
                    $start_ts = (int) strtotime('-' . (string) $past_offset_days . ' days 09:00');
                }
            }
            $duration_hours = 4 + (($i % 4) * 2);
            $end_ts = $start_ts + ($duration_hours * HOUR_IN_SECONDS);
            if ($i % 9 === 0) {
                $end_ts = $start_ts + (2 * DAY_IN_SECONDS);
            }

            $start = wp_date('Y-m-d H:i:s', $start_ts);
            $end = wp_date('Y-m-d H:i:s', $end_ts);
            $event_slug = eep_sanitize_title($topic_title . '-' . $location['city'] . '-' . ($i + 1));
            $item_path = eep_sanitize_title($plural_label);
            if ($item_path === '') {
                $item_path = 'events';
            }
            $event_url = 'https://example.com/' . $item_path . '/' . $event_slug;
            $contact_email = $is_single_org_demo
                ? $single_org_contact_email
                : ('listing+' . (string) ($i + 1) . '@nexdirectory.example');
            $speaker_profiles = [];
            if ($is_events_profile) {
                $speaker_count = 3 + ($i % 3);
                for ($speaker_index = 0; $speaker_index < $speaker_count; $speaker_index++) {
                    $speaker_gender = wp_rand(0, 1) === 0 ? 'woman' : 'man';
                    $speaker_first_name_pool = $speaker_gender === 'woman' ? $female_first_names : $male_first_names;
                    $speaker_first_name = $speaker_first_name_pool[wp_rand(0, count($speaker_first_name_pool) - 1)];
                    $speaker_last_name = $last_names[wp_rand(0, count($last_names) - 1)];
                    $speaker_prefix = $speaker_index % 2 === 0 ? 'Dr. ' : '';
                    $speaker_name = $speaker_prefix . $speaker_first_name . ' ' . $speaker_last_name;
                    $speaker_profiles[] = [
                        'name' => sanitize_text_field((string) $speaker_name),
                        'profession' => sanitize_text_field((string) ($speaker_professions[$speaker_index % count($speaker_professions)] ?? 'Speaker')),
                        'subject' => sanitize_text_field((string) $topic_type),
                        'image' => $this->pick_demo_headshot_url($speaker_gender, [$cover_image]),
                        'details' => sprintf(
                            'Leads practical sessions and live discussion around %s for this event.',
                            sanitize_text_field((string) strtolower($topic_type))
                        ),
                    ];
                }
            }

            $sessions = [];
            if ($is_events_profile) {
                $event_span_days = max(1, (int) floor(max(0, $end_ts - $start_ts) / DAY_IN_SECONDS) + 1);
                $day_count = min(3, $event_span_days);
                if ($day_count < 2 && $i % 5 === 0) {
                    $day_count = 2;
                }

                $session_templates = [
                    ['title' => 'Opening Session', 'offset_minutes' => 0, 'duration_minutes' => 90, 'location_suffix' => '', 'attachment_slot' => 0],
                    ['title' => 'Technical Deep Dive', 'offset_minutes' => 120, 'duration_minutes' => 110, 'location_suffix' => ' - Hall B', 'attachment_slot' => 1],
                    ['title' => 'Workshop Sprint', 'offset_minutes' => 270, 'duration_minutes' => 100, 'location_suffix' => ' - Studio', 'attachment_slot' => 2],
                    ['title' => 'Q&A and Networking', 'offset_minutes' => 410, 'duration_minutes' => 75, 'location_suffix' => ' - Lounge', 'attachment_slot' => 3],
                ];

                for ($day_offset = 0; $day_offset < $day_count; $day_offset++) {
                    $day_start_ts = (int) strtotime('+' . (string) $day_offset . ' days', $start_ts);
                    $day_label = wp_date('F j, Y', $day_start_ts);
                    foreach ($session_templates as $template_index => $template) {
                        $template = is_array($template) ? $template : [];
                        if ($day_offset === ($day_count - 1) && $template_index === 2 && $day_count > 1) {
                            continue;
                        }

                        $session_start_ts = $day_start_ts + ((int) ($template['offset_minutes'] ?? 0) * MINUTE_IN_SECONDS);
                        $session_end_ts = $session_start_ts + ((int) ($template['duration_minutes'] ?? 60) * MINUTE_IN_SECONDS);
                        $speaker_profile = !empty($speaker_profiles)
                            ? (array) ($speaker_profiles[($template_index + $day_offset) % count($speaker_profiles)] ?? [])
                            : [];
                        $session_attachments = [];
                        $session_attachments[] = $template_index % 2 === 0 ? $doc_1 : $doc_2;
                        if (!empty($gallery_urls_for_row)) {
                            $session_attachments[] = (string) ($gallery_urls_for_row[($template_index + $day_offset) % count($gallery_urls_for_row)] ?? '');
                        }
                        if ($template_index === 0 && $day_offset === 0) {
                            $session_attachments[] = 'dQw4w9WgXcQ';
                        }
                        $session_attachments = array_values(array_unique(array_filter(array_map('sanitize_text_field', $session_attachments))));
                        $session_title = sanitize_text_field((string) ($template['title'] ?? __('Session', 'nexdirectory')));
                        if ($day_count > 1) {
                            $session_title .= ' (Day ' . (string) ($day_offset + 1) . ')';
                        }
                        $session_notes = sprintf(
                            '%1$s %2$s',
                            (string) ($session_notes_pool[($i + $template_index) % count($session_notes_pool)] ?? ''),
                            sprintf('Track focus: %s.', sanitize_text_field((string) $topic_type))
                        );
                        $sessions[] = [
                            'title' => $session_title,
                            'start' => wp_date('Y-m-d H:i:s', $session_start_ts),
                            'end' => wp_date('Y-m-d H:i:s', $session_end_ts),
                            'location' => sanitize_text_field((string) $location['venue'] . (string) ($template['location_suffix'] ?? '')),
                            'speaker' => sanitize_text_field((string) ($speaker_profile['name'] ?? '')),
                            'day' => $day_label,
                            'notes' => $session_notes,
                            'attachments' => $session_attachments,
                            'session_key' => sanitize_key('demo-' . (string) ($i + 1) . '-d' . (string) ($day_offset + 1) . '-s' . (string) ($template_index + 1)),
                        ];
                    }
                }
            } elseif ($is_job_profile) {
                $sessions = [];
            } else {
                $sessions = [
                    [
                        'title' => 'Open For Enquiries',
                        'start' => $start,
                        'end' => wp_date('Y-m-d H:i:s', $start_ts + (8 * HOUR_IN_SECONDS)),
                        'location' => $location['city'],
                        'day' => wp_date('F j, Y', $start_ts),
                        'notes' => 'Team members are available throughout this window.',
                        'attachments' => [$doc_1],
                        'session_key' => sanitize_key('demo-' . (string) ($i + 1) . '-window-1'),
                    ],
                    [
                        'title' => 'Priority Booking Window',
                        'start' => wp_date('Y-m-d H:i:s', $start_ts + (9 * HOUR_IN_SECONDS)),
                        'end' => wp_date('Y-m-d H:i:s', $start_ts + (11 * HOUR_IN_SECONDS)),
                        'location' => $location['venue'],
                        'day' => wp_date('F j, Y', $start_ts),
                        'notes' => 'Limited slots are prioritised during this period.',
                        'attachments' => [$doc_2],
                        'session_key' => sanitize_key('demo-' . (string) ($i + 1) . '-window-2'),
                    ],
                ];
            }

            $key_speakers = $is_events_profile ? $speaker_profiles : [];
            $topic_tag_options = array_values(array_filter(array_map('eep_sanitize_text', array_map('trim', explode('|', (string) $topic_tags))), static function ($value) {
                return $value !== '';
            }));
            $topic_tag_options[] = sanitize_text_field((string) $topic_type);
            $topic_tag_options[] = sanitize_text_field((string) $location['city']);
            $topic_tag_options = array_values(array_unique(array_filter($topic_tag_options, static function ($value) {
                return trim((string) $value) !== '';
            })));
            if (empty($topic_tag_options)) {
                $topic_tag_options = ['General'];
            }
            $track_options = implode('|', array_slice($topic_tag_options, 0, 6));
            if ($track_options === '') {
                $track_options = 'General';
            }

            $custom_schema = $is_job_profile ? [] : [
                [
                    'id' => 'lead_sponsor',
                    'label' => $is_events_profile ? 'Lead Sponsor' : 'Primary Partner',
                    'type' => 'text',
                    'placeholder' => 'Sponsor company',
                    'help' => $is_events_profile ? 'Primary sponsor supporting this event.' : 'Primary partner supporting this listing.',
                    'required' => '0',
                    'show_in_event' => '1',
                    'options' => '',
                ],
                [
                    'id' => 'resource_pack',
                    'label' => 'Resource Pack URL',
                    'type' => 'url',
                    'placeholder' => 'https://',
                    'help' => $is_events_profile ? 'Public resource package for attendees.' : 'Public brochure, menu, or brochure URL.',
                    'required' => '0',
                    'show_in_event' => '1',
                    'options' => '',
                ],
                [
                    'id' => 'event_track',
                    'label' => $is_events_profile ? 'Event Track' : ($singular_label . ' Category'),
                    'type' => 'select',
                    'placeholder' => '',
                    'help' => $is_events_profile ? 'Main topic track for this edition.' : 'Main category for this listing.',
                    'required' => '0',
                    'show_in_event' => '1',
                    'options' => $track_options,
                ],
                [
                    'id' => 'certificates_available',
                    'label' => $is_events_profile ? 'Certificates Available' : 'Featured Listing',
                    'type' => 'checkbox',
                    'placeholder' => '',
                    'help' => $is_events_profile ? 'Whether attendees can request participation certificates.' : 'Highlight this listing as featured.',
                    'required' => '0',
                    'show_in_event' => '1',
                    'options' => '',
                ],
                [
                    'id' => 'speaker_notes',
                    'label' => $is_events_profile ? 'Speaker Notes' : 'Owner Notes',
                    'type' => 'wysiwyg',
                    'placeholder' => '',
                    'help' => $is_events_profile ? 'Short organizer note shown on event page.' : 'Short owner note shown on the listing page.',
                    'required' => '0',
                    'show_in_event' => '1',
                    'options' => '',
                ],
            ];

            $mode = $topic_mode;
            $stream_url = ($is_events_profile && ($mode === 'Online Stream Event' || $mode === 'Hybrid')) ? $video_1 : '';
            $title_value = $is_events_profile
                ? ($is_single_org_demo
                    ? $brand_name . ': ' . $topic_title . ' - ' . $location['city']
                    : $topic_title . ' ' . (string) (2026 + (int) floor($i / 25)) . ' - ' . $location['city'])
                : $topic_title . ' - ' . $location['city'];
            $description_value = $is_events_profile
                ? ($is_single_org_demo
                    ? sprintf(
                        'Official %1$s programme item for %2$s in %3$s. This demo reflects a single-company event calendar with ticketing, sessions, media, map pinning, and moderation-ready metadata.',
                        $topic_title,
                        $brand_name,
                        $location['city']
                    )
                    : sprintf(
                        '%5$s demo %6$s %1$d showcasing submissions, map pins, media galleries, and moderation workflow for %2$s in %3$s. This sample includes full metadata, sessions, taxonomy terms, links, and downloadable documents for %4$s.',
                        (int) ($i + 1),
                        $topic_title,
                        $location['city'],
                        $brand_name,
                        $profile_label,
                        $singular_lower
                    ))
                : ($is_job_profile ? sprintf(
                    '%5$s demo job listing %1$d for %2$s in %3$s. This sample includes salary, role requirements, interview process, location setup, and full apply methods for %4$s.',
                    (int) ($i + 1),
                    $topic_title,
                    $location['city'],
                    $brand_name,
                    $profile_label
                ) : sprintf(
                    '%5$s demo %6$s %1$d showcasing directory workflows for %2$s in %3$s. This sample includes listing metadata, media assets, documents, taxonomy terms, and moderation/claiming flow for %4$s.',
                    (int) ($i + 1),
                    $topic_title,
                    $location['city'],
                    $brand_name,
                    $profile_label,
                    $singular_lower
                ));
            $excerpt_value = $is_events_profile
                ? ($is_single_org_demo
                    ? sprintf(
                        '%1$s official programme entry in %2$s with sessions, media, and attendee-ready details.',
                        $topic_title,
                        $location['city']
                    )
                    : sprintf(
                        'Demo %3$s for %1$s in %2$s. Includes images, videos, documents, sessions, and custom fields.',
                        $topic_title,
                        $location['city'],
                        $singular_lower
                    ))
                : ($is_job_profile ? sprintf(
                    'Demo job listing for %1$s in %2$s. Includes salary, deadline, and application details.',
                    $topic_title,
                    $location['city']
                ) : sprintf(
                    'Demo %3$s for %1$s in %2$s. Includes listing media, docs, business details, and custom fields.',
                    $topic_title,
                    $location['city'],
                    $singular_lower
                ));
            $is_recurring_topic = false;
            if ($is_events_profile) {
                if ($is_single_org_demo) {
                    $recurring_markers = ['meetup', 'mixer', 'forum', 'roadshow', 'open day'];
                    $title_lc = strtolower((string) $topic_title);
                    foreach ($recurring_markers as $marker) {
                        if (strpos($title_lc, (string) $marker) !== false) {
                            $is_recurring_topic = true;
                            break;
                        }
                    }
                } else {
                    $is_recurring_topic = $i % 7 === 0;
                }
            }
            $recurrence_value = $is_recurring_topic ? ($is_single_org_demo ? 'monthly' : 'weekly') : 'none';
            $recurrence_until_value = $is_recurring_topic ? wp_date('Y-m-d', strtotime('+8 weeks', max($start_ts, time()))) : '';
            $job_work_mode = 'on-site';
            if (stripos($topic_mode, 'hybrid') !== false) {
                $job_work_mode = 'hybrid';
            } elseif (stripos($topic_mode, 'online') !== false || stripos($topic_mode, 'remote') !== false || stripos($topic_mode, 'video') !== false) {
                $job_work_mode = 'remote';
            }
            $job_type = (string) ($topic['job_type'] ?? ($i % 6 === 0 ? 'Contract' : ($i % 3 === 0 ? 'Part-time' : 'Full-time')));
            $job_salary = $topic_price_label !== '' ? $topic_price_label : '$55,000 - $75,000';
            $job_department = (string) ($topic['department'] ?? ($topic_type !== '' ? $topic_type : 'Operations'));
            $job_apply_url = 'https://example.com/careers/' . $event_slug;
            $job_application_email = 'jobs+' . (string) ($i + 1) . '@nexdirectory.example';
            $job_closing_date = wp_date('Y-m-d', strtotime('+21 days', $start_ts));
            $job_expected_start_date = wp_date('Y-m-d', strtotime('+45 days', $start_ts));
            $job_remote_policy = $job_work_mode === 'hybrid' ? 'Hybrid (2-3 days on-site)' : ($job_work_mode === 'remote' ? 'Remote-first' : 'On-site');
            $job_skills = implode(', ', array_slice($topic_tag_options, 0, 5));
            $job_company_website = 'https://example.com/companies/' . eep_sanitize_title($brand_name . '-' . $location['city']);
            $job_company_size = ($i % 4 === 0) ? '201-500 employees' : (($i % 3 === 0) ? '51-200 employees' : '11-50 employees');
            $job_industry = $topic_type !== '' ? $topic_type : 'Technology';
            $email_templates = $is_events_profile
                ? [
                    'eep_email_subject_confirmation' => 'You are confirmed for {event_title}',
                    'eep_email_subject_reminder' => 'Reminder: {event_title} starts soon',
                    'eep_email_subject_update' => 'Update for {event_title}',
                    'eep_email_subject_cancellation' => '{event_title} has been cancelled',
                    'eep_email_body_confirmation' => "Hi {organizer_name},\n\nThanks for creating {event_title}. It is now listed for attendees.\n\nDate: {event_date}\nView: {event_url}",
                    'eep_email_body_reminder' => "Hi {organizer_name},\n\nFriendly reminder for {event_title}.\nDate: {event_date}\nURL: {event_url}",
                    'eep_email_body_update' => "Hi {organizer_name},\n\nYour event {event_title} was updated.\nDate: {event_date}\nURL: {event_url}",
                    'eep_email_body_cancellation' => "Hi {organizer_name},\n\n{event_title} has been cancelled.\nIf needed, create a new listing: {event_url}",
                ]
                : [
                    'eep_email_subject_confirmation' => 'Your listing is in review: {event_title}',
                    'eep_email_subject_reminder' => 'Reminder: review your listing {event_title}',
                    'eep_email_subject_update' => 'Listing update: {event_title}',
                    'eep_email_subject_cancellation' => 'Listing status changed: {event_title}',
                    'eep_email_body_confirmation' => "Hi {organizer_name},\n\nThanks for creating {event_title}. Your listing is now in review.\n\nDate: {event_date}\nView: {event_url}",
                    'eep_email_body_reminder' => "Hi {organizer_name},\n\nQuick reminder to review your listing details for {event_title}.\nDate: {event_date}\nURL: {event_url}",
                    'eep_email_body_update' => "Hi {organizer_name},\n\nYour listing {event_title} was updated.\nDate: {event_date}\nURL: {event_url}",
                    'eep_email_body_cancellation' => "Hi {organizer_name},\n\n{event_title} changed status. Please review and submit updates if needed.\nURL: {event_url}",
                ];

            $demo_custom_values = $is_job_profile ? [] : [
                'lead_sponsor' => $brand_name . ($is_events_profile ? ' Labs' : ' Network'),
                'resource_pack' => $event_url . '/resources',
                'event_track' => $topic_tag_options[$i % count($topic_tag_options)],
                'certificates_available' => $i % 2 === 0 ? '1' : '0',
                'speaker_notes' => '<p>Doors open 30 minutes early. Bring ID for fast check-in and network with peers from around the world.</p>',
            ];
            $ticket_sales_enabled = $is_events_profile && (stripos($topic_price_label, 'free') === false || ($i % 3 === 0));
            $ticket_price_value = '';
            if ($ticket_sales_enabled && preg_match('/([0-9]+(?:[.,][0-9]{1,2})?)/', $topic_price_label, $ticket_price_match)) {
                $ticket_price_value = number_format((float) str_replace(',', '.', (string) ($ticket_price_match[1] ?? '0')), 2, '.', '');
            }
            if ($ticket_sales_enabled && $ticket_price_value === '') {
                $ticket_price_value = number_format((float) (49 + (($i % 5) * 25)), 2, '.', '');
            }

            $webapp_enabled_value = $is_events_profile ? '1' : '0';
            $webapp_icon_url = esc_url_raw((string) ($logo_images[$i % count($logo_images)] ?? $cover_image));
            if ($webapp_icon_url === '') {
                $webapp_icon_url = $cover_image;
            }
            $webapp_header_logo_url = esc_url_raw((string) ($logo_images[($i + 1) % count($logo_images)] ?? $webapp_icon_url));
            if ($webapp_header_logo_url === '') {
                $webapp_header_logo_url = $webapp_icon_url;
            }
            $webapp_asset_urls = array_values(array_unique(array_filter(array_map('esc_url_raw', array_merge(
                [$doc_1, $doc_2, $video_1],
                array_slice($gallery_urls_for_row, 0, 3)
            )))));
            $webapp_push_provider = $i % 3 === 0 ? 'onesignal' : 'custom';
            $webapp_chat_moderation = $i % 4 === 0 ? 'moderated' : 'open';
            $webapp_qa_moderation = $i % 5 === 0 ? 'moderated' : 'open';
            $webapp_polls_vote_mode = $i % 3 === 0 ? 'multiple' : 'single';
            $rows[] = [
                'title' => $title_value,
                'description' => $description_value,
                'excerpt' => $excerpt_value,
                'start' => $start,
                'end' => $end,
                'timezone' => $location['timezone'],
                'venue' => $location['venue'],
                'address' => $location['address'],
                'city' => $location['city'],
                'country' => $location['country'],
                'lat' => $location['lat'],
                'lng' => $location['lng'],
                'capacity' => (string) (120 + (($i % 8) * 35)),
                'price' => $topic_price_label,
                'ticket_sales_enabled' => $ticket_sales_enabled ? '1' : '0',
                'ticket_price' => $ticket_price_value,
                'ticket_button_label' => $is_events_profile ? 'Get Tickets' : '',
                'external_url' => $event_url,
                'stream_url' => $stream_url,
                'contact_email' => $contact_email,
                'contact_phone' => '+1-202-555-' . str_pad((string) (1000 + $i), 4, '0', STR_PAD_LEFT),
                'contact_name' => $brand_name . ($is_events_profile ? ' Organizer Team' : ' Listing Team'),
                'cover_image_url' => $cover_image,
                'supporting_docs_urls' => $doc_1 . "\n" . $doc_2,
                'official_event' => $topic_official,
                'recurrence' => $recurrence_value,
                'recurrence_until' => $recurrence_until_value,
                'video_urls' => $video_1 . "\n" . $video_2,
                'gallery_urls' => implode("\n", $gallery_urls_for_row),
                'sessions' => wp_json_encode($sessions),
                'key_speakers' => wp_json_encode($key_speakers),
                'event_type' => $topic_type,
                'event_mode' => $topic_mode,
                'event_price_range' => $topic_price_range,
                'event_tags' => $topic_tags . '|' . $location['city'] . ($is_single_org_demo ? ('|' . $brand_name) : ''),
                'company_name' => $is_job_profile ? ($brand_name . ' ' . $location['city']) : '',
                'job_type' => $is_job_profile ? $job_type : '',
                'job_work_mode' => $is_job_profile ? $job_work_mode : '',
                'job_salary' => $is_job_profile ? $job_salary : '',
                'job_apply_url' => $is_job_profile ? $job_apply_url : '',
                'job_application_email' => $is_job_profile ? $job_application_email : '',
                'job_closing_date' => $is_job_profile ? $job_closing_date : '',
                'job_department' => $is_job_profile ? $job_department : '',
                'job_experience_level' => $is_job_profile ? (($i % 4 === 0) ? 'Senior' : (($i % 3 === 0) ? 'Mid-level' : 'Entry-level')) : '',
                'job_reporting_to' => $is_job_profile ? (($i % 2 === 0) ? 'Head of Department' : 'Hiring Manager') : '',
                'job_remote_policy' => $is_job_profile ? $job_remote_policy : '',
                'job_visa_sponsorship' => $is_job_profile ? (($i % 5 === 0) ? 'yes' : 'no') : '',
                'job_open_positions' => $is_job_profile ? (string) (1 + ($i % 4)) : '',
                'job_interview_process' => $is_job_profile ? 'Screening call, role-specific interview, final interview.' : '',
                'job_expected_start_date' => $is_job_profile ? $job_expected_start_date : '',
                'job_skills' => $is_job_profile ? $job_skills : '',
                'job_responsibilities' => $is_job_profile ? 'Lead key projects, collaborate with cross-functional teams, and deliver measurable outcomes.' : '',
                'job_requirements' => $is_job_profile ? 'Relevant professional experience, clear communication skills, and strong problem-solving ability.' : '',
                'job_benefits' => $is_job_profile ? 'Competitive salary, pension contribution, paid leave, and development budget.' : '',
                'job_company_website' => $is_job_profile ? $job_company_website : '',
                'job_company_size' => $is_job_profile ? $job_company_size : '',
                'job_industry' => $is_job_profile ? $job_industry : '',
                'eep_webapp_enabled' => $webapp_enabled_value,
                'eep_webapp_title' => $is_events_profile ? sanitize_text_field((string) ($topic_title . ' App')) : '',
                'eep_webapp_subtitle' => $is_events_profile ? sprintf('Official attendee app for %s in %s.', $topic_title, $location['city']) : '',
                'eep_webapp_icon_url' => $is_events_profile ? $webapp_icon_url : '',
                'eep_webapp_splash_url' => $is_events_profile ? $cover_image : '',
                'eep_webapp_header_logo_url' => $is_events_profile ? $webapp_header_logo_url : '',
                'eep_webapp_accent_color' => $is_events_profile ? $webapp_accent : '',
                'eep_webapp_loading_bg_color' => $is_events_profile ? $webapp_loading_background : '',
                'eep_webapp_push_enabled' => $is_events_profile ? '1' : '0',
                'eep_webapp_push_provider' => $is_events_profile ? $webapp_push_provider : 'none',
                'eep_webapp_push_key' => $is_events_profile ? sanitize_text_field((string) ('demo-push-key-' . ($i + 1))) : '',
                'eep_webapp_requires_login' => $is_events_profile ? '0' : '1',
                'eep_webapp_allowed_user_ids' => '',
                'eep_webapp_chat_moderation' => $is_events_profile ? $webapp_chat_moderation : 'open',
                'eep_webapp_qa_moderation' => $is_events_profile ? $webapp_qa_moderation : 'open',
                'eep_webapp_qa_allow_attendee_answers' => $is_events_profile ? '1' : '0',
                'eep_webapp_qa_allow_anonymous' => '0',
                'eep_webapp_polls_vote_mode' => $is_events_profile ? $webapp_polls_vote_mode : 'single',
                'eep_webapp_enable_chat' => $is_events_profile ? '1' : '0',
                'eep_webapp_enable_qa' => $is_events_profile ? '1' : '0',
                'eep_webapp_enable_polls' => $is_events_profile ? '1' : '0',
                'eep_webapp_enable_map' => $is_events_profile ? '1' : '0',
                'eep_webapp_enable_notifications' => $is_events_profile ? '1' : '0',
                'eep_webapp_enable_attendees' => $is_events_profile ? '1' : '0',
                'eep_webapp_enable_custom_itinerary' => $is_events_profile ? '1' : '0',
                'eep_webapp_asset_urls' => $is_events_profile ? implode("\n", $webapp_asset_urls) : '',
                'status' => 'publish',
                'demo_event_stream_url' => $stream_url,
                'demo_custom_schema' => $custom_schema,
                'demo_custom_values' => $demo_custom_values,
                'demo_email_templates' => $email_templates,
            ];
        }

        return $rows;
    }


    private function apply_demo_import_post_enrichment($post_id, array $row, $index = 0) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return;
        }

        $stream_url = esc_url_raw((string) ($row['demo_event_stream_url'] ?? ''));
        if ($stream_url !== '') {
            update_post_meta($post_id, 'eep_event_stream_url', $stream_url);
        }

        $custom_schema = isset($row['demo_custom_schema']) && is_array($row['demo_custom_schema']) ? $row['demo_custom_schema'] : [];
        $custom_values = isset($row['demo_custom_values']) && is_array($row['demo_custom_values']) ? $row['demo_custom_values'] : [];
        if (!empty($custom_schema)) {
            update_post_meta($post_id, 'eep_custom_fields_schema', wp_json_encode($custom_schema));
        }
        if (!empty($custom_values)) {
            update_post_meta($post_id, 'eep_custom_fields_data', wp_json_encode($custom_values));
        }

        $email_templates = isset($row['demo_email_templates']) && is_array($row['demo_email_templates']) ? $row['demo_email_templates'] : [];
        foreach ($email_templates as $meta_key => $meta_value) {
            $meta_key = sanitize_key((string) $meta_key);
            if ($meta_key === '' || strpos($meta_key, 'eep_email_') !== 0) {
                continue;
            }
            $is_body = strpos($meta_key, 'body') !== false;
            $sanitized_value = $is_body ? eep_sanitize_textarea((string) $meta_value) : sanitize_text_field((string) $meta_value);
            update_post_meta($post_id, $meta_key, $sanitized_value);
        }
        $this->ensure_event_owner_team_member($post_id);

        update_post_meta($post_id, 'eep_demo_imported', '1');
        update_post_meta($post_id, 'eep_demo_locked', '1');
        update_post_meta($post_id, 'eep_demo_imported_at', current_time('mysql'));
        update_post_meta($post_id, 'eep_demo_import_batch', gmdate('Ymd-His'));
        update_post_meta($post_id, 'eep_demo_item_index', (string) ((int) $index + 1));
    }


    private function seed_demo_import_runtime_data(array $event_ids, array $rows_by_post_id = [], $import_user_id = 0) {
        $summary = [
            'analytics_rows' => 0,
            'boost_orders' => 0,
            'messages' => 0,
            'threads' => 0,
            'rsvps' => 0,
            'ticket_orders' => 0,
            'pending_posts' => 0,
            'draft_posts' => 0,
            'claim_requests' => 0,
            'reports' => 0,
            'webapp_events' => 0,
            'webapp_chat' => 0,
            'webapp_qa' => 0,
            'webapp_polls' => 0,
            'webapp_notifications' => 0,
            'webapp_team_members' => 0,
            'webapp_itineraries' => 0,
        ];

        $event_ids = array_values(array_unique(array_filter(array_map('absint', $event_ids))));
        if (empty($event_ids)) {
            return $summary;
        }

        $owner_map = [];
        foreach ($event_ids as $event_id) {
            $owner_map[$event_id] = absint((int) get_post_field('post_author', $event_id));
        }

        $user_pool = $this->get_demo_seed_user_pool($import_user_id);
        $organizer_ids = array_values(array_filter(array_unique(array_map('absint', (array) ($user_pool['organizers'] ?? [])))));
        $attendee_ids = array_values(array_filter(array_unique(array_map('absint', (array) ($user_pool['attendees'] ?? [])))));
        if (empty($attendee_ids)) {
            $attendee_ids = array_values(array_filter(array_map('absint', (array) get_users([
                'number' => 30,
                'orderby' => 'ID',
                'order' => 'ASC',
                'fields' => 'ID',
            ]))));
        }

        $summary['analytics_rows'] = $this->seed_demo_import_analytics_data($event_ids, $owner_map, $attendee_ids);
        $summary['boost_orders'] = $this->seed_demo_import_boost_data($event_ids, $owner_map);

        $inbox_seed = $this->seed_demo_import_inbox_data($event_ids, $owner_map, $attendee_ids);
        if (is_array($inbox_seed)) {
            $summary['threads'] = absint((int) ($inbox_seed['threads'] ?? 0));
            $summary['messages'] = absint((int) ($inbox_seed['messages'] ?? 0));
        }

        $rsvp_seed = $this->seed_demo_import_attendance_data($event_ids, $owner_map, $attendee_ids, $rows_by_post_id);
        if (is_array($rsvp_seed)) {
            $summary['rsvps'] = absint((int) ($rsvp_seed['rsvps'] ?? 0));
            $summary['ticket_orders'] = absint((int) ($rsvp_seed['ticket_orders'] ?? 0));
        }

        $moderation_seed = $this->seed_demo_import_moderation_data($event_ids, $attendee_ids);
        if (is_array($moderation_seed)) {
            $summary['pending_posts'] = absint((int) ($moderation_seed['pending_posts'] ?? 0));
            $summary['draft_posts'] = absint((int) ($moderation_seed['draft_posts'] ?? 0));
            $summary['claim_requests'] = absint((int) ($moderation_seed['claim_requests'] ?? 0));
            $summary['reports'] = absint((int) ($moderation_seed['reports'] ?? 0));
        }

        $webapp_seed = $this->seed_demo_import_webapp_data($event_ids, $owner_map, $attendee_ids, $rows_by_post_id, $organizer_ids);
        if (is_array($webapp_seed)) {
            $summary['webapp_events'] = absint((int) ($webapp_seed['webapp_events'] ?? 0));
            $summary['webapp_chat'] = absint((int) ($webapp_seed['webapp_chat'] ?? 0));
            $summary['webapp_qa'] = absint((int) ($webapp_seed['webapp_qa'] ?? 0));
            $summary['webapp_polls'] = absint((int) ($webapp_seed['webapp_polls'] ?? 0));
            $summary['webapp_notifications'] = absint((int) ($webapp_seed['webapp_notifications'] ?? 0));
            $summary['webapp_team_members'] = absint((int) ($webapp_seed['webapp_team_members'] ?? 0));
            $summary['webapp_itineraries'] = absint((int) ($webapp_seed['webapp_itineraries'] ?? 0));
        }

        return $summary;
    }


    private function get_demo_seed_user_pool($import_user_id = 0) {
        $import_user_id = absint((int) $import_user_id);
        $organizers = [];
        $attendees = [];

        if ($import_user_id > 0) {
            $organizers[] = $import_user_id;
        }

        $existing_organizers = get_users([
            'number' => 10,
            'orderby' => 'ID',
            'order' => 'ASC',
            'fields' => 'ID',
            'role__in' => ['administrator', 'editor', 'author'],
        ]);
        foreach ((array) $existing_organizers as $user_id) {
            $user_id = absint((int) $user_id);
            if ($user_id > 0) {
                $organizers[] = $user_id;
            }
        }
        $organizers = array_values(array_unique(array_filter(array_map('absint', $organizers))));

        $existing_attendees = get_users([
            'number' => 40,
            'orderby' => 'ID',
            'order' => 'ASC',
            'fields' => 'ID',
            'role__in' => ['subscriber', 'contributor', 'author'],
        ]);
        foreach ((array) $existing_attendees as $user_id) {
            $user_id = absint((int) $user_id);
            if ($user_id > 0) {
                $attendees[] = $user_id;
            }
        }

        $demo_organizers = [
            ['login' => 'eep_demo_organizer_1', 'email' => 'demo.organizer.1@example.com', 'name' => 'Demo Organizer One', 'role' => 'author'],
            ['login' => 'eep_demo_organizer_2', 'email' => 'demo.organizer.2@example.com', 'name' => 'Demo Organizer Two', 'role' => 'author'],
            ['login' => 'eep_demo_organizer_3', 'email' => 'demo.organizer.3@example.com', 'name' => 'Demo Organizer Three', 'role' => 'author'],
        ];
        foreach ($demo_organizers as $seed_user) {
            $user_id = $this->upsert_demo_seed_user($seed_user);
            if ($user_id > 0) {
                $organizers[] = $user_id;
                $attendees[] = $user_id;
            }
        }

        $demo_attendees = [
            ['login' => 'eep_demo_member_1', 'email' => 'demo.member.1@example.com', 'name' => 'Demo Member One', 'role' => 'subscriber'],
            ['login' => 'eep_demo_member_2', 'email' => 'demo.member.2@example.com', 'name' => 'Demo Member Two', 'role' => 'subscriber'],
            ['login' => 'eep_demo_member_3', 'email' => 'demo.member.3@example.com', 'name' => 'Demo Member Three', 'role' => 'subscriber'],
            ['login' => 'eep_demo_member_4', 'email' => 'demo.member.4@example.com', 'name' => 'Demo Member Four', 'role' => 'subscriber'],
            ['login' => 'eep_demo_member_5', 'email' => 'demo.member.5@example.com', 'name' => 'Demo Member Five', 'role' => 'subscriber'],
            ['login' => 'eep_demo_member_6', 'email' => 'demo.member.6@example.com', 'name' => 'Demo Member Six', 'role' => 'subscriber'],
            ['login' => 'eep_demo_member_7', 'email' => 'demo.member.7@example.com', 'name' => 'Demo Member Seven', 'role' => 'subscriber'],
            ['login' => 'eep_demo_member_8', 'email' => 'demo.member.8@example.com', 'name' => 'Demo Member Eight', 'role' => 'subscriber'],
            ['login' => 'eep_demo_member_9', 'email' => 'demo.member.9@example.com', 'name' => 'Demo Member Nine', 'role' => 'subscriber'],
            ['login' => 'eep_demo_member_10', 'email' => 'demo.member.10@example.com', 'name' => 'Demo Member Ten', 'role' => 'subscriber'],
            ['login' => 'eep_demo_member_11', 'email' => 'demo.member.11@example.com', 'name' => 'Demo Member Eleven', 'role' => 'subscriber'],
            ['login' => 'eep_demo_member_12', 'email' => 'demo.member.12@example.com', 'name' => 'Demo Member Twelve', 'role' => 'subscriber'],
        ];
        foreach ($demo_attendees as $seed_user) {
            $user_id = $this->upsert_demo_seed_user($seed_user);
            if ($user_id > 0) {
                $attendees[] = $user_id;
            }
        }

        $organizers = array_values(array_unique(array_filter(array_map('absint', $organizers))));
        $attendees = array_values(array_unique(array_filter(array_map('absint', $attendees))));
        if (!empty($organizers)) {
            $attendees = array_values(array_unique(array_merge($attendees, $organizers)));
        }

        return [
            'organizers' => $organizers,
            'attendees' => $attendees,
        ];
    }


    private function upsert_demo_seed_user(array $seed_user = []) {
        $login = sanitize_user((string) ($seed_user['login'] ?? ''), true);
        $email = sanitize_email((string) ($seed_user['email'] ?? ''));
        $name = sanitize_text_field((string) ($seed_user['name'] ?? ''));
        $role = sanitize_key((string) ($seed_user['role'] ?? 'subscriber'));
        if ($login === '' || !is_email($email)) {
            return 0;
        }
        if (!in_array($role, ['subscriber', 'contributor', 'author', 'editor'], true)) {
            $role = 'subscriber';
        }

        $user = get_user_by('email', $email);
        if (!$user instanceof WP_User) {
            $user = get_user_by('login', $login);
        }
        if (!$user instanceof WP_User) {
            $password = wp_generate_password(20, true, true);
            $created = wp_insert_user([
                'user_login' => $login,
                'user_pass' => $password,
                'user_email' => $email,
                'display_name' => $name !== '' ? $name : $login,
                'nickname' => $name !== '' ? $name : $login,
                'role' => $role,
            ]);
            if (is_wp_error($created)) {
                return 0;
            }
            $user = get_user_by('id', absint((int) $created));
        }
        if (!$user instanceof WP_User) {
            return 0;
        }

        $user_id = (int) $user->ID;
        if ($user_id <= 0) {
            return 0;
        }
        if ($name !== '') {
            wp_update_user([
                'ID' => $user_id,
                'display_name' => $name,
                'nickname' => $name,
            ]);
        }
        update_user_meta($user_id, 'eep_demo_seed_user', '1');
        update_user_meta($user_id, 'eep_marketing_opt_in', '1');
        update_user_meta($user_id, 'eep_event_messages_enabled', '1');
        update_user_meta($user_id, 'eep_gdpr_terms_accepted_at', (string) time());

        return $user_id;
    }


    private function seed_demo_import_analytics_data(array $event_ids, array $owner_map = [], array $attendee_ids = []) {
        global $wpdb;
        $table = (string) $wpdb->prefix . self::ANALYTICS_TABLE;
        $table_exists = (string) $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($table_exists !== $table) {
            return 0;
        }

        $row_count = 0;
        $events_count = max(1, count($event_ids));
        $target_rows = 12000;
        $metrics_per_day = 7;
        $days_window = max(7, min(30, (int) floor($target_rows / ($events_count * $metrics_per_day))));
        foreach ($event_ids as $event_id) {
            $event_id = absint((int) $event_id);
            if ($event_id <= 0) {
                continue;
            }
            $owner_id = absint((int) ($owner_map[$event_id] ?? get_post_field('post_author', $event_id)));
            if ($owner_id <= 0) {
                $owner_id = absint((int) get_current_user_id());
            }

            for ($day = 0; $day < $days_window; $day++) {
                $base_ts = strtotime('-' . $day . ' days');
                if (!$base_ts) {
                    continue;
                }
                $created_at = gmdate('Y-m-d H:i:s', $base_ts + rand(0, 86000));
                $viewer_id = !empty($attendee_ids) ? absint((int) $attendee_ids[array_rand($attendee_ids)]) : 0;

                $listing_views = rand(16, 140);
                $map_impressions = $listing_views + rand(8, 120);
                $search_appearances = $listing_views + rand(5, 90);
                $click_phone = rand(0, max(2, (int) floor($listing_views / 7)));
                $click_website = rand(1, max(3, (int) floor($listing_views / 5)));
                $click_whatsapp = rand(0, max(1, (int) floor($listing_views / 9)));
                $enquiries = rand(0, max(2, (int) floor($listing_views / 12)));

                $rows = [
                    ['metric' => 'listing_view', 'value' => $listing_views, 'context' => 'single'],
                    ['metric' => 'map_impression', 'value' => $map_impressions, 'context' => 'map'],
                    ['metric' => 'search_appearance', 'value' => $search_appearances, 'context' => 'search'],
                    ['metric' => 'click_phone', 'value' => $click_phone, 'context' => 'single'],
                    ['metric' => 'click_website', 'value' => $click_website, 'context' => 'single'],
                    ['metric' => 'click_whatsapp', 'value' => $click_whatsapp, 'context' => 'single'],
                    ['metric' => 'enquiry_received', 'value' => $enquiries, 'context' => 'inbox'],
                ];

                foreach ($rows as $row) {
                    if ((float) ($row['value'] ?? 0) <= 0) {
                        continue;
                    }
                    $inserted = $wpdb->insert(
                        $table,
                        [
                            'event_id' => $event_id,
                            'owner_id' => $owner_id,
                            'user_id' => $viewer_id,
                            'metric' => sanitize_key((string) ($row['metric'] ?? '')),
                            'context' => sanitize_key((string) ($row['context'] ?? '')),
                            'meta_key' => '',
                            'meta_value' => '',
                            'value' => (float) ($row['value'] ?? 1),
                            'created_at' => $created_at,
                        ],
                        ['%d', '%d', '%d', '%s', '%s', '%s', '%s', '%f', '%s']
                    ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                    if ($inserted !== false) {
                        $row_count++;
                    }
                }
            }
        }

        $global_metrics = ['search_filter', 'search_query', 'map_area_search'];
        $meta_keys = ['event_type', 'event_mode', 'event_tag', 'city'];
        $meta_values = ['conference', 'hybrid', 'featured', 'london', 'new-york', 'sao-paulo', 'singapore'];
        $search_queries = ['innovation summit', 'networking', 'green energy', 'future tech', 'aerospace'];
        $global_rows = max(40, min(220, count($event_ids) * 5));
        for ($index = 0; $index < $global_rows; $index++) {
            $metric = (string) $global_metrics[array_rand($global_metrics)];
            $meta_key = $metric === 'search_query' ? 'query' : (string) $meta_keys[array_rand($meta_keys)];
            $meta_value = $metric === 'search_query'
                ? (string) $search_queries[array_rand($search_queries)]
                : (string) $meta_values[array_rand($meta_values)];
            $created_at = gmdate('Y-m-d H:i:s', time() - rand(0, 40 * DAY_IN_SECONDS));
            $viewer_id = !empty($attendee_ids) ? absint((int) $attendee_ids[array_rand($attendee_ids)]) : 0;
            $inserted = $wpdb->insert(
                $table,
                [
                    'event_id' => 0,
                    'owner_id' => 0,
                    'user_id' => $viewer_id,
                    'metric' => sanitize_key($metric),
                    'context' => $metric === 'map_area_search' ? 'map' : 'search',
                    'meta_key' => sanitize_key($meta_key),
                    'meta_value' => sanitize_text_field($meta_value),
                    'value' => (float) rand(1, 6),
                    'created_at' => $created_at,
                ],
                ['%d', '%d', '%d', '%s', '%s', '%s', '%s', '%f', '%s']
            ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
            if ($inserted !== false) {
                $row_count++;
            }
        }

        return $row_count;
    }


    private function seed_demo_import_boost_data(array $event_ids, array $owner_map = []) {
        if (!method_exists($this, 'create_boost_order_record') || !method_exists($this, 'activate_boost_from_order')) {
            return 0;
        }
        if (method_exists($this, 'create_boost_orders_table')) {
            $this->create_boost_orders_table();
        }

        $seed_ids = array_values(array_unique(array_filter(array_map('absint', $event_ids))));
        if (empty($seed_ids)) {
            return 0;
        }
        shuffle($seed_ids);
        $target = max(1, min((int) floor(count($seed_ids) * 0.35), 24));
        $seed_ids = array_slice($seed_ids, 0, $target);
        $count = 0;
        $currencies = ['USD', 'EUR', 'GBP'];
        $price_points = [9.99, 14.99, 24.99, 39.99];
        $durations = [7, 14, 30, 60];

        foreach ($seed_ids as $event_id) {
            $owner_id = absint((int) ($owner_map[$event_id] ?? get_post_field('post_author', $event_id)));
            if ($owner_id <= 0) {
                $owner_id = absint((int) get_current_user_id());
            }
            $amount = (float) $price_points[array_rand($price_points)];
            $duration = (int) $durations[array_rand($durations)];
            $currency = (string) $currencies[array_rand($currencies)];
            $created_at = gmdate('Y-m-d H:i:s', time() - rand(2 * DAY_IN_SECONDS, 55 * DAY_IN_SECONDS));

            $order = $this->create_boost_order_record([
                'post_id' => $event_id,
                'user_id' => $owner_id,
                'gateway' => 'test_free',
                'order_status' => 'paid',
                'currency' => $currency,
                'subtotal' => $amount,
                'vat_amount' => 0,
                'total_amount' => $amount,
                'duration_days' => $duration,
                'auto_renew' => rand(0, 1),
                'external_order_id' => 'demo-boost-' . wp_generate_uuid4(),
                'external_payment_id' => 'demo-boost-pay-' . wp_generate_password(10, false, false),
                'customer_ref' => 'demo-owner-' . $owner_id,
                'payment_method_ref' => 'demo-card',
                'payment_meta' => wp_json_encode(['source' => 'demo_import']),
                'paid_at' => $created_at,
                'created_at' => $created_at,
                'updated_at' => $created_at,
            ]);
            if (!is_array($order) || empty($order['id'])) {
                continue;
            }

            $activation = $this->activate_boost_from_order((int) $order['id'], [
                'gateway' => 'test_free',
                'external_order_id' => (string) ($order['external_order_id'] ?? ''),
                'external_payment_id' => (string) ($order['external_payment_id'] ?? ''),
                'customer_ref' => (string) ($order['customer_ref'] ?? ''),
                'payment_method_ref' => (string) ($order['payment_method_ref'] ?? ''),
                'raw' => ['source' => 'demo_import'],
            ]);
            if (!is_wp_error($activation)) {
                $count++;
            }
        }

        return $count;
    }


    private function seed_demo_import_inbox_data(array $event_ids, array $owner_map = [], array $attendee_ids = []) {
        $out = [
            'threads' => 0,
            'messages' => 0,
        ];
        if (!method_exists($this, 'inbox_send_message')) {
            return $out;
        }

        $event_ids = array_values(array_unique(array_filter(array_map('absint', $event_ids))));
        $attendee_ids = array_values(array_unique(array_filter(array_map('absint', $attendee_ids))));
        if (empty($event_ids) || empty($attendee_ids)) {
            return $out;
        }

        global $wpdb;
        $threads_table = $this->get_inbox_threads_table_name();
        $messages_table = $this->get_inbox_messages_table_name();
        $thread_ids = [];

        foreach ($event_ids as $event_id) {
            $owner_id = absint((int) ($owner_map[$event_id] ?? get_post_field('post_author', $event_id)));
            if ($owner_id <= 0) {
                continue;
            }

            $participants = array_values(array_diff($attendee_ids, [$owner_id]));
            if (empty($participants)) {
                continue;
            }
            shuffle($participants);
            $conversation_count = min(count($participants), rand(1, 3));
            $event_title = sanitize_text_field((string) get_the_title($event_id));

            for ($i = 0; $i < $conversation_count; $i++) {
                $sender_id = absint((int) ($participants[$i] ?? 0));
                if ($sender_id <= 0) {
                    continue;
                }

                $subject = sprintf(__('Question about %s', 'nexdirectory'), $event_title);
                $first_message = $this->inbox_send_message(
                    $event_id,
                    $sender_id,
                    $owner_id,
                    $subject,
                    __('Hi, I am interested in this listing. Can you confirm start times and what is included for attendees?', 'nexdirectory')
                );
                if (!is_array($first_message)) {
                    continue;
                }

                $thread_id = absint((int) ($first_message['thread_id'] ?? 0));
                $message_id = absint((int) ($first_message['message_id'] ?? 0));
                if ($thread_id > 0) {
                    $thread_ids[$thread_id] = $thread_id;
                }
                if ($message_id > 0) {
                    $message_time = gmdate('Y-m-d H:i:s', time() - rand(1 * DAY_IN_SECONDS, 28 * DAY_IN_SECONDS));
                    $wpdb->update(
                        $messages_table,
                        ['created_at' => $message_time],
                        ['id' => $message_id],
                        ['%s'],
                        ['%d']
                    ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                    if ($thread_id > 0) {
                        $wpdb->update(
                            $threads_table,
                            ['updated_at' => $message_time, 'last_message_id' => $message_id, 'last_sender_id' => $sender_id],
                            ['id' => $thread_id],
                            ['%s', '%d', '%d'],
                            ['%d']
                        ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                    }
                }
                $out['messages']++;

                if (rand(1, 100) <= 85) {
                    $reply = $this->inbox_send_message(
                        $event_id,
                        $owner_id,
                        $sender_id,
                        $subject,
                        __('Thanks for reaching out. Yes, everything is confirmed and you can RSVP directly from the event page.', 'nexdirectory')
                    );
                    if (is_array($reply)) {
                        $reply_message_id = absint((int) ($reply['message_id'] ?? 0));
                        $reply_thread_id = absint((int) ($reply['thread_id'] ?? $thread_id));
                        $reply_time = gmdate('Y-m-d H:i:s', time() - rand(1 * DAY_IN_SECONDS, 20 * DAY_IN_SECONDS));
                        if ($reply_message_id > 0) {
                            $wpdb->update(
                                $messages_table,
                                ['created_at' => $reply_time],
                                ['id' => $reply_message_id],
                                ['%s'],
                                ['%d']
                            ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                        }
                        if ($reply_thread_id > 0) {
                            $wpdb->update(
                                $threads_table,
                                ['updated_at' => $reply_time, 'last_message_id' => $reply_message_id, 'last_sender_id' => $owner_id],
                                ['id' => $reply_thread_id],
                                ['%s', '%d', '%d'],
                                ['%d']
                            ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                        }
                        $out['messages']++;
                    }
                }
            }
        }

        $out['threads'] = count($thread_ids);
        return $out;
    }


    private function seed_demo_import_attendance_data(array $event_ids, array $owner_map = [], array $attendee_ids = [], array $rows_by_post_id = []) {
        $out = [
            'rsvps' => 0,
            'ticket_orders' => 0,
        ];
        $event_ids = array_values(array_unique(array_filter(array_map('absint', $event_ids))));
        $attendee_ids = array_values(array_unique(array_filter(array_map('absint', $attendee_ids))));
        if (empty($event_ids) || empty($attendee_ids)) {
            return $out;
        }

        global $wpdb;
        $rsvp_table = $this->get_rsvp_table_name();
        $table_exists = (string) $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $rsvp_table));
        if ($table_exists !== $rsvp_table) {
            return $out;
        }

        $ticket_currency = strtoupper(sanitize_text_field((string) get_option('eep_ticket_currency', 'USD')));
        if ($ticket_currency === '') {
            $ticket_currency = 'USD';
        }
        $fallback_ticket_prices = [0, 0, 9.99, 15.00, 25.00, 39.00];

        foreach ($event_ids as $event_id) {
            $owner_id = absint((int) ($owner_map[$event_id] ?? get_post_field('post_author', $event_id)));
            $eligible_attendees = array_values(array_diff($attendee_ids, [$owner_id]));
            if (empty($eligible_attendees)) {
                continue;
            }
            shuffle($eligible_attendees);
            $max_take = min(count($eligible_attendees), rand(3, 10));
            if ($max_take <= 0) {
                continue;
            }

            $event_row = is_array($rows_by_post_id[$event_id] ?? null) ? (array) $rows_by_post_id[$event_id] : [];
            $ticket_sales_enabled = rand(1, 100) <= 55;
            $price_hint = trim((string) ($event_row['price'] ?? get_post_meta($event_id, 'eep_price', true)));
            $ticket_price = 0.0;
            if ($ticket_sales_enabled) {
                if (preg_match('/([0-9]+(?:[.,][0-9]{1,2})?)/', $price_hint, $price_match)) {
                    $ticket_price = (float) str_replace(',', '.', (string) ($price_match[1] ?? '0'));
                }
                if ($ticket_price <= 0) {
                    $ticket_price = (float) $fallback_ticket_prices[array_rand($fallback_ticket_prices)];
                }
            }

            update_post_meta($event_id, 'eep_ticket_sales_enabled', $ticket_sales_enabled ? '1' : '0');
            if ($ticket_sales_enabled) {
                update_post_meta($event_id, 'eep_ticket_price', number_format(max(0, $ticket_price), 2, '.', ''));
            }

            for ($i = 0; $i < $max_take; $i++) {
                $user_id = absint((int) ($eligible_attendees[$i] ?? 0));
                if ($user_id <= 0) {
                    continue;
                }
                $status = $i === 0 || rand(1, 100) > 18 ? 'yes' : 'waitlist';
                $created_at = gmdate('Y-m-d H:i:s', time() - rand(1 * DAY_IN_SECONDS, 35 * DAY_IN_SECONDS));
                $saved = $wpdb->replace(
                    $rsvp_table,
                    [
                        'post_id' => $event_id,
                        'user_id' => $user_id,
                        'status' => $status,
                        'created_at' => $created_at,
                        'updated_at' => $created_at,
                    ],
                    ['%d', '%d', '%s', '%s', '%s']
                ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                if ($saved !== false) {
                    $out['rsvps']++;
                }

                if (
                    $ticket_sales_enabled
                    && $status === 'yes'
                    && method_exists($this, 'create_ticket_order_record')
                    && method_exists($this, 'get_ticket_orders_table_name')
                ) {
                    $gateway = $ticket_price > 0 ? 'stripe' : 'test_free';
                    $ticket_order = $this->create_ticket_order_record(
                        [
                            'post_id' => $event_id,
                            'user_id' => $user_id,
                            'amount' => max(0, (float) $ticket_price),
                            'currency' => $ticket_currency,
                            'gateway' => $gateway,
                            'context_key' => 'demo-ticket-' . wp_generate_password(10, false, false),
                            'purchase_ip' => '127.0.0.1',
                            'purchase_user_agent' => 'NexDirectory Demo Seeder',
                        ],
                        [
                            'gateway' => $gateway,
                            'external_order_id' => 'demo-ticket-order-' . wp_generate_password(8, false, false),
                            'external_payment_id' => 'demo-ticket-pay-' . wp_generate_password(8, false, false),
                            'customer_ref' => 'demo-user-' . $user_id,
                            'payment_method_ref' => $gateway === 'stripe' ? 'demo-card' : 'demo-free',
                            'raw' => [
                                'source' => 'demo_import',
                                'captured_at' => $created_at,
                            ],
                        ],
                        'yes'
                    );
                    if (is_array($ticket_order) && !empty($ticket_order['id'])) {
                        $ticket_order_table = $this->get_ticket_orders_table_name();
                        $wpdb->update(
                            $ticket_order_table,
                            [
                                'purchased_at' => $created_at,
                                'created_at' => $created_at,
                                'updated_at' => $created_at,
                                'email_sent' => 1,
                                'email_sent_at' => $created_at,
                            ],
                            ['id' => absint((int) $ticket_order['id'])],
                            ['%s', '%s', '%s', '%d', '%s'],
                            ['%d']
                        ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                        $out['ticket_orders']++;
                    }
                }
            }
            $this->sync_rsvp_meta_from_table($event_id);
        }

        return $out;
    }


    private function seed_demo_import_moderation_data(array $event_ids, array $attendee_ids = []) {
        $out = [
            'pending_posts' => 0,
            'draft_posts' => 0,
            'claim_requests' => 0,
            'reports' => 0,
        ];
        $event_ids = array_values(array_unique(array_filter(array_map('absint', $event_ids))));
        if (empty($event_ids)) {
            return $out;
        }

        $mutable_ids = $event_ids;
        shuffle($mutable_ids);
        $pending_target = max(1, min((int) floor(count($mutable_ids) * 0.12), 10));
        $draft_target = max(1, min((int) floor(count($mutable_ids) * 0.08), 8));

        for ($i = 0; $i < $pending_target; $i++) {
            $post_id = absint((int) ($mutable_ids[$i] ?? 0));
            if ($post_id <= 0) {
                continue;
            }
            $updated = wp_update_post([
                'ID' => $post_id,
                'post_status' => 'pending',
            ], true);
            if (!is_wp_error($updated)) {
                $out['pending_posts']++;
            }
        }

        for ($i = $pending_target; $i < ($pending_target + $draft_target); $i++) {
            $post_id = absint((int) ($mutable_ids[$i] ?? 0));
            if ($post_id <= 0) {
                continue;
            }
            $updated = wp_update_post([
                'ID' => $post_id,
                'post_status' => 'draft',
            ], true);
            if (!is_wp_error($updated)) {
                $out['draft_posts']++;
            }
        }

        $report_ids = $event_ids;
        shuffle($report_ids);
        $comment_target = max(2, min((int) floor(count($report_ids) * 0.4), 20));
        $attendee_ids = array_values(array_unique(array_filter(array_map('absint', $attendee_ids))));

        for ($i = 0; $i < $comment_target; $i++) {
            $post_id = absint((int) ($report_ids[$i] ?? 0));
            if ($post_id <= 0) {
                continue;
            }
            $user_id = !empty($attendee_ids) ? absint((int) $attendee_ids[array_rand($attendee_ids)]) : 0;
            $user = $user_id > 0 ? get_user_by('id', $user_id) : null;
            $author_name = $user instanceof WP_User ? (string) ($user->display_name ?: $user->user_login) : __('Demo User', 'nexdirectory');
            $author_email = $user instanceof WP_User ? sanitize_email((string) $user->user_email) : 'demo.user@example.com';
            $comment_time = gmdate('Y-m-d H:i:s', time() - rand(1 * DAY_IN_SECONDS, 20 * DAY_IN_SECONDS));

            if ($i % 2 === 0) {
                $comment_id = wp_insert_comment([
                    'comment_post_ID' => $post_id,
                    'comment_author' => $author_name,
                    'comment_author_email' => $author_email,
                    'comment_author_url' => '',
                    'comment_content' => __('Demo report: details need review for this listing.', 'nexdirectory'),
                    'comment_type' => 'eep_report',
                    'user_id' => $user_id,
                    'comment_approved' => 1,
                    'comment_date' => get_date_from_gmt($comment_time),
                    'comment_date_gmt' => $comment_time,
                ]);
                if ($comment_id > 0) {
                    $out['reports']++;
                }
            } else {
                $comment_id = wp_insert_comment([
                    'comment_post_ID' => $post_id,
                    'comment_author' => $author_name,
                    'comment_author_email' => $author_email,
                    'comment_author_url' => '',
                    'comment_content' => __('Demo claim request: requesting ownership transfer for this listing.', 'nexdirectory'),
                    'comment_type' => 'eep_claim',
                    'user_id' => $user_id,
                    'comment_approved' => 0,
                    'comment_date' => get_date_from_gmt($comment_time),
                    'comment_date_gmt' => $comment_time,
                ]);
                if ($comment_id > 0) {
                    update_comment_meta((int) $comment_id, 'eep_claim_status', 'pending');
                    update_comment_meta((int) $comment_id, 'eep_claim_requested_at', get_date_from_gmt($comment_time));
                    $out['claim_requests']++;
                }
            }
        }

        return $out;
    }


    private function seed_demo_import_webapp_data(array $event_ids, array $owner_map = [], array $attendee_ids = [], array $rows_by_post_id = [], array $organizer_ids = []) {
        $out = [
            'webapp_events' => 0,
            'webapp_chat' => 0,
            'webapp_qa' => 0,
            'webapp_polls' => 0,
            'webapp_notifications' => 0,
            'webapp_team_members' => 0,
            'webapp_itineraries' => 0,
        ];

        $event_ids = array_values(array_unique(array_filter(array_map('absint', $event_ids))));
        if (empty($event_ids)) {
            return $out;
        }

        $attendee_ids = array_values(array_unique(array_filter(array_map('absint', $attendee_ids))));
        $organizer_ids = array_values(array_unique(array_filter(array_map('absint', $organizer_ids))));
        $accent_palette = ['#4F46E5', '#EA580C', '#0EA5E9', '#16A34A', '#B91C1C'];
        $chat_templates = [
            'Welcome to %s. Use this feed for live updates.',
            'Can anyone share the best route to the venue entrance?',
            'Session slides are now attached inside itinerary cards.',
            'Networking meetup starts in 15 minutes near the lounge.',
            'Reminder: save sessions to My Schedule before they begin.',
            'Great insights so far. Looking forward to the next speaker.',
        ];
        $qa_templates = [
            'Will slides and attachments be available after the event?',
            'Can attendees submit follow-up questions after this session?',
            'Is there a replay link for the workshop track?',
            'Which session is best for first-time attendees?',
            'Will there be a downloadable PDF summary after closing?',
        ];
        $poll_templates = [
            ['question' => 'Which topic should be expanded in the next session?', 'options' => ['AI Automation', 'Infrastructure', 'Design Systems', 'Product Ops']],
            ['question' => 'How are you attending today?', 'options' => ['In person', 'Remote stream', 'Hybrid', 'On-demand replay']],
            ['question' => 'What was most useful so far?', 'options' => ['Keynote', 'Hands-on workshop', 'Networking', 'Q&A']],
        ];
        $notification_templates = [
            ['text' => 'Venue doors are open. Check-in is now live.', 'type' => 'info'],
            ['text' => 'Room update: Workshop sessions moved to Hall B.', 'type' => 'warning'],
            ['text' => 'New resources uploaded to itinerary attachments.', 'type' => 'success'],
            ['text' => 'Live Q&A is active. Vote questions in the app.', 'type' => 'alert'],
        ];

        foreach ($event_ids as $event_index => $event_id) {
            $event_id = absint((int) $event_id);
            if ($event_id <= 0 || get_post_type($event_id) !== self::POST_TYPE) {
                continue;
            }

            $owner_id = absint((int) ($owner_map[$event_id] ?? get_post_field('post_author', $event_id)));
            if ($owner_id <= 0) {
                $owner_id = absint((int) get_current_user_id());
            }
            if ($owner_id <= 0) {
                continue;
            }
            $owner_user = get_userdata($owner_id);
            $owner_name = $owner_user instanceof WP_User
                ? sanitize_text_field((string) ($owner_user->display_name ?: $owner_user->user_login))
                : __('Event Owner', 'nexdirectory');
            $event_title = sanitize_text_field((string) get_the_title($event_id));

            $seed_row = isset($rows_by_post_id[$event_id]) && is_array($rows_by_post_id[$event_id]) ? $rows_by_post_id[$event_id] : [];
            $cover_url = esc_url_raw((string) ($seed_row['cover_image_url'] ?? get_post_meta($event_id, 'eep_cover_image_url', true)));
            $icon_url = esc_url_raw((string) ($seed_row['eep_webapp_icon_url'] ?? get_post_meta($event_id, 'eep_webapp_icon_url', true)));
            if ($icon_url === '') {
                $icon_url = $cover_url;
            }
            $header_logo_url = esc_url_raw((string) ($seed_row['eep_webapp_header_logo_url'] ?? get_post_meta($event_id, 'eep_webapp_header_logo_url', true)));
            if ($header_logo_url === '') {
                $header_logo_url = $icon_url;
            }
            $splash_url = esc_url_raw((string) ($seed_row['eep_webapp_splash_url'] ?? $cover_url));
            $asset_urls = array_values(array_unique(array_filter(array_map('esc_url_raw', array_merge(
                $this->parse_multiline_urls((string) ($seed_row['supporting_docs_urls'] ?? '')),
                $this->parse_multiline_urls((string) ($seed_row['video_urls'] ?? '')),
                $this->parse_multiline_urls((string) ($seed_row['gallery_urls'] ?? ''))
            )))));
            if ($cover_url !== '') {
                $asset_urls[] = $cover_url;
            }
            $asset_urls = array_values(array_slice(array_unique(array_filter($asset_urls)), 0, 10));

            $session_rows = $this->build_event_webapp_session_rows($event_id);
            $webapp_enabled = !empty($session_rows) || (string) get_post_meta($event_id, 'eep_webapp_enabled', true) === '1';
            if (!$webapp_enabled) {
                continue;
            }

            update_post_meta($event_id, 'eep_webapp_enabled', '1');
            update_post_meta($event_id, 'eep_webapp_title', $event_title !== '' ? ($event_title . ' App') : __('Event App', 'nexdirectory'));
            update_post_meta($event_id, 'eep_webapp_subtitle', sprintf(__('Official attendee app for %s.', 'nexdirectory'), $event_title !== '' ? $event_title : __('this event', 'nexdirectory')));
            if ($icon_url !== '') {
                update_post_meta($event_id, 'eep_webapp_icon_url', $icon_url);
            }
            if ($splash_url !== '') {
                update_post_meta($event_id, 'eep_webapp_splash_url', $splash_url);
            }
            if ($header_logo_url !== '') {
                update_post_meta($event_id, 'eep_webapp_header_logo_url', $header_logo_url);
            }
            update_post_meta($event_id, 'eep_webapp_accent_color', (string) ($accent_palette[$event_index % count($accent_palette)] ?? '#4F46E5'));
            update_post_meta($event_id, 'eep_webapp_loading_bg_color', '#FFFFFF');
            update_post_meta($event_id, 'eep_webapp_push_enabled', '1');
            update_post_meta($event_id, 'eep_webapp_push_provider', $event_index % 2 === 0 ? 'onesignal' : 'custom');
            update_post_meta($event_id, 'eep_webapp_push_key', sanitize_text_field((string) ('demo-push-key-' . $event_id)));
            update_post_meta($event_id, 'eep_webapp_requires_login', '0');
            update_post_meta($event_id, 'eep_webapp_chat_moderation', $event_index % 4 === 0 ? 'moderated' : 'open');
            update_post_meta($event_id, 'eep_webapp_qa_moderation', $event_index % 5 === 0 ? 'moderated' : 'open');
            update_post_meta($event_id, 'eep_webapp_qa_allow_attendee_answers', '1');
            update_post_meta($event_id, 'eep_webapp_qa_allow_anonymous', '0');
            update_post_meta($event_id, 'eep_webapp_polls_vote_mode', $event_index % 3 === 0 ? 'multiple' : 'single');
            update_post_meta($event_id, 'eep_webapp_enable_chat', '1');
            update_post_meta($event_id, 'eep_webapp_enable_qa', '1');
            update_post_meta($event_id, 'eep_webapp_enable_polls', '1');
            update_post_meta($event_id, 'eep_webapp_enable_map', '1');
            update_post_meta($event_id, 'eep_webapp_enable_notifications', '1');
            update_post_meta($event_id, 'eep_webapp_enable_attendees', '1');
            update_post_meta($event_id, 'eep_webapp_enable_custom_itinerary', '1');
            if (!empty($asset_urls)) {
                update_post_meta($event_id, 'eep_webapp_asset_urls', implode("\n", $asset_urls));
            }
            $out['webapp_events']++;

            $this->ensure_event_owner_team_member($event_id);
            $team_rows = $this->get_event_team_members($event_id);
            $team_candidates = array_values(array_unique(array_filter(array_map('absint', array_merge($organizer_ids, $attendee_ids)))));
            $team_candidates = array_values(array_diff($team_candidates, [$owner_id]));
            if (!empty($team_candidates)) {
                shuffle($team_candidates);
                $roles = ['editor', 'app_moderator', 'support'];
                $dirty = false;
                $team_target = min(3, count($team_candidates));
                for ($member_index = 0; $member_index < $team_target; $member_index++) {
                    $member_user_id = absint((int) ($team_candidates[$member_index] ?? 0));
                    if ($member_user_id <= 0) {
                        continue;
                    }
                    $member_user = get_userdata($member_user_id);
                    if (!$member_user instanceof WP_User) {
                        continue;
                    }
                    $member_email = strtolower(sanitize_email((string) $member_user->user_email));
                    if ($member_email === '') {
                        continue;
                    }
                    if ($this->get_event_team_member_index($team_rows, '', $member_email, $member_user_id) >= 0) {
                        continue;
                    }
                    $team_rows[] = [
                        'id' => wp_generate_uuid4(),
                        'email' => $member_email,
                        'user_id' => $member_user_id,
                        'role' => $roles[$member_index % count($roles)],
                        'status' => 'active',
                        'scope_website' => $member_index === 0 ? '1' : '0',
                        'scope_webapp' => '1',
                        'invited_by' => $owner_id,
                        'invited_at' => current_time('mysql', true),
                        'accepted_at' => current_time('mysql', true),
                        'token' => '',
                    ];
                    $dirty = true;
                    $out['webapp_team_members']++;
                }
                if ($dirty) {
                    $this->save_event_team_members($event_id, $team_rows);
                }
            }

            $participant_ids = array_values(array_unique(array_filter(array_map('absint', array_merge([$owner_id], $attendee_ids)))));
            if (empty($participant_ids)) {
                continue;
            }
            shuffle($participant_ids);

            $chat_rows = [];
            $chat_count = max(5, min(10, 5 + ($event_index % 4)));
            for ($chat_index = 0; $chat_index < $chat_count; $chat_index++) {
                $sender_id = absint((int) ($participant_ids[$chat_index % count($participant_ids)] ?? $owner_id));
                $sender_user = get_userdata($sender_id);
                $sender_name = $sender_user instanceof WP_User
                    ? sanitize_text_field((string) ($sender_user->display_name ?: $sender_user->user_login))
                    : __('Member', 'nexdirectory');
                $chat_body = sprintf(
                    (string) ($chat_templates[$chat_index % count($chat_templates)] ?? 'Welcome to %s.'),
                    $event_title !== '' ? $event_title : __('this event', 'nexdirectory')
                );
                $chat_rows[] = [
                    'id' => $this->event_webapp_generate_id('chat'),
                    'user_id' => $sender_id,
                    'user_name' => $sender_name,
                    'body' => $chat_body,
                    'status' => ($chat_index === ($chat_count - 1) && $event_index % 3 === 0) ? 'pending' : 'approved',
                    'created_at_gmt' => gmdate('Y-m-d H:i:s', time() - ((($chat_count - $chat_index) * 13) * MINUTE_IN_SECONDS)),
                ];
            }
            $this->update_event_webapp_store_rows($event_id, 'eep_webapp_chat_messages', $chat_rows, 500);
            $out['webapp_chat'] += count($chat_rows);

            $qa_rows = [];
            $qa_count = 3 + ($event_index % 3);
            for ($qa_index = 0; $qa_index < $qa_count; $qa_index++) {
                $asker_id = absint((int) ($participant_ids[($qa_index + 1) % count($participant_ids)] ?? $owner_id));
                $asker_user = get_userdata($asker_id);
                $asker_name = $asker_user instanceof WP_User
                    ? sanitize_text_field((string) ($asker_user->display_name ?: $asker_user->user_login))
                    : __('Member', 'nexdirectory');

                $vote_candidates = $participant_ids;
                shuffle($vote_candidates);
                $vote_take = min(count($vote_candidates), 2 + ($qa_index % 4));
                $voters = array_values(array_slice($vote_candidates, 0, $vote_take));
                $answer_text = $qa_index % 2 === 0 ? __('Yes, this will be shared with attendees after the session.', 'nexdirectory') : '';
                $qa_rows[] = [
                    'id' => $this->event_webapp_generate_id('qa'),
                    'question' => (string) ($qa_templates[$qa_index % count($qa_templates)] ?? __('Will this be recorded?', 'nexdirectory')),
                    'user_id' => $asker_id,
                    'user_name' => $asker_name,
                    'answer' => $answer_text,
                    'answer_user_id' => $answer_text !== '' ? $owner_id : 0,
                    'answer_user_name' => $answer_text !== '' ? $owner_name : '',
                    'voters' => $voters,
                    'status' => ($qa_index === ($qa_count - 1) && $event_index % 4 === 0) ? 'hidden' : 'open',
                    'created_at_gmt' => gmdate('Y-m-d H:i:s', time() - ((($qa_count - $qa_index) * 29) * MINUTE_IN_SECONDS)),
                    'answered_at_gmt' => $answer_text !== '' ? gmdate('Y-m-d H:i:s', time() - ((($qa_count - $qa_index) * 17) * MINUTE_IN_SECONDS)) : '',
                ];
            }
            $this->update_event_webapp_store_rows($event_id, 'eep_webapp_qa_items', $qa_rows, 400);
            $out['webapp_qa'] += count($qa_rows);

            $poll_rows = [];
            $poll_count = 2;
            for ($poll_index = 0; $poll_index < $poll_count; $poll_index++) {
                $poll_template = (array) ($poll_templates[($event_index + $poll_index) % count($poll_templates)] ?? []);
                $poll_question = sanitize_text_field((string) ($poll_template['question'] ?? __('Quick poll', 'nexdirectory')));
                $poll_options_seed = array_values(array_filter(array_map('sanitize_text_field', (array) ($poll_template['options'] ?? []))));
                if (count($poll_options_seed) < 2) {
                    $poll_options_seed = [__('Option A', 'nexdirectory'), __('Option B', 'nexdirectory')];
                }
                $multiple = ($poll_index + $event_index) % 2 === 0;
                $poll_options = [];
                foreach ($poll_options_seed as $option_index => $option_label) {
                    $poll_options[] = [
                        'id' => sanitize_key($this->event_webapp_generate_id('opt')),
                        'label' => $option_label,
                        'voters' => [],
                    ];
                }
                $vote_candidates = array_values(array_diff($participant_ids, [$owner_id]));
                if (empty($vote_candidates)) {
                    $vote_candidates = $participant_ids;
                }
                if ($multiple) {
                    foreach ($poll_options as $option_index => $option_row) {
                        $candidate_pool = $vote_candidates;
                        shuffle($candidate_pool);
                        $vote_take = min(count($candidate_pool), 1 + (($option_index + $event_index) % 3));
                        $poll_options[$option_index]['voters'] = array_values(array_slice($candidate_pool, 0, $vote_take));
                    }
                } else {
                    foreach ($vote_candidates as $voter_id) {
                        $vote_option_index = wp_rand(0, max(0, count($poll_options) - 1));
                        $poll_options[$vote_option_index]['voters'][] = absint((int) $voter_id);
                    }
                }
                foreach ($poll_options as $option_index => $option_row) {
                    $poll_options[$option_index]['voters'] = array_values(array_unique(array_filter(array_map('absint', (array) ($option_row['voters'] ?? [])))));
                }
                $poll_rows[] = [
                    'id' => $this->event_webapp_generate_id('poll'),
                    'question' => $poll_question,
                    'status' => ($poll_index === 1 && $event_index % 6 === 0) ? 'closed' : 'open',
                    'multiple' => $multiple,
                    'options' => $poll_options,
                    'created_by_user_id' => $owner_id,
                    'created_by_name' => $owner_name,
                    'created_at_gmt' => gmdate('Y-m-d H:i:s', time() - ((($poll_count - $poll_index) * 37) * MINUTE_IN_SECONDS)),
                ];
            }
            $this->update_event_webapp_store_rows($event_id, 'eep_webapp_polls_feed', $poll_rows, 240);
            $out['webapp_polls'] += count($poll_rows);

            $notification_rows = [];
            $notification_count = 3;
            for ($notification_index = 0; $notification_index < $notification_count; $notification_index++) {
                $notification_template = (array) ($notification_templates[($event_index + $notification_index) % count($notification_templates)] ?? []);
                $notification_rows[] = [
                    'id' => $this->event_webapp_generate_id('notif'),
                    'text' => sanitize_text_field((string) ($notification_template['text'] ?? __('Event update posted.', 'nexdirectory'))),
                    'type' => sanitize_key((string) ($notification_template['type'] ?? 'info')),
                    'author_id' => $owner_id,
                    'author_name' => $owner_name,
                    'created_at_gmt' => gmdate('Y-m-d H:i:s', time() - ((($notification_count - $notification_index) * 23) * MINUTE_IN_SECONDS)),
                ];
            }
            $this->update_event_webapp_store_rows($event_id, 'eep_webapp_notifications_feed', $notification_rows, 300);
            $out['webapp_notifications'] += count($notification_rows);

            if (!empty($session_rows)) {
                $session_keys = array_values(array_unique(array_filter(array_map(static function ($session_row) {
                    return sanitize_key((string) ($session_row['id'] ?? ''));
                }, $session_rows))));
                if (!empty($session_keys)) {
                    $itinerary_users = array_values(array_slice($participant_ids, 0, min(6, count($participant_ids))));
                    foreach ($itinerary_users as $itinerary_index => $itinerary_user_id) {
                        $itinerary_user_id = absint((int) $itinerary_user_id);
                        if ($itinerary_user_id <= 0) {
                            continue;
                        }
                        $pick_pool = $session_keys;
                        shuffle($pick_pool);
                        $save_count = min(count($pick_pool), 1 + (($itinerary_index + $event_index) % min(4, count($pick_pool))));
                        if ($save_count <= 0) {
                            continue;
                        }
                        $saved_keys = array_values(array_slice($pick_pool, 0, $save_count));
                        $notes = [];
                        $first_key = sanitize_key((string) ($saved_keys[0] ?? ''));
                        if ($first_key !== '') {
                            $notes[$first_key] = __('Save this session early to secure your seat and prep notes.', 'nexdirectory');
                        }
                        $this->update_event_webapp_user_itinerary($event_id, $itinerary_user_id, $saved_keys, $notes);
                        $out['webapp_itineraries']++;
                    }
                }
            }
        }

        return $out;
    }


    private function build_event_export_row($post_id) {
        $post_id = absint($post_id);
        $post = get_post($post_id);
        if (!$post || $post->post_type !== self::POST_TYPE) {
            return [];
        }

        $cover_image_url = (string) get_post_meta($post_id, 'eep_cover_image_url', true);
        if ($cover_image_url === '' && has_post_thumbnail($post_id)) {
            $thumb = get_the_post_thumbnail_url($post_id, 'full');
            if (is_string($thumb) && $thumb !== '') {
                $cover_image_url = $thumb;
            }
        }

        $row = [
            'title' => (string) $post->post_title,
            'description' => (string) $post->post_content,
            'excerpt' => (string) $post->post_excerpt,
            'start' => (string) get_post_meta($post_id, 'eep_start', true),
            'end' => (string) get_post_meta($post_id, 'eep_end', true),
            'timezone' => (string) get_post_meta($post_id, 'eep_timezone', true),
            'venue' => (string) get_post_meta($post_id, 'eep_venue', true),
            '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),
            'lat' => (string) get_post_meta($post_id, 'eep_lat', true),
            'lng' => (string) get_post_meta($post_id, 'eep_lng', true),
            'capacity' => (string) get_post_meta($post_id, 'eep_capacity', true),
            'price' => (string) get_post_meta($post_id, 'eep_price', true),
            'external_url' => (string) get_post_meta($post_id, 'eep_external_url', true),
            'contact_email' => (string) get_post_meta($post_id, 'eep_contact_email', true),
            'contact_phone' => (string) get_post_meta($post_id, 'eep_contact_phone', true),
            'contact_name' => (string) get_post_meta($post_id, 'eep_contact_name', true),
            'cover_image_url' => $cover_image_url,
            'supporting_docs_urls' => implode('|', $this->parse_multiline_urls((string) get_post_meta($post_id, 'eep_supporting_docs', true))),
            'official_event' => $this->get_official_event_flag($post_id) ? 'yes' : 'no',
            'recurrence' => (string) get_post_meta($post_id, 'eep_recurrence', true),
            'recurrence_until' => (string) get_post_meta($post_id, 'eep_recurrence_until', true),
            'video_urls' => implode('|', $this->parse_multiline_urls((string) get_post_meta($post_id, 'eep_video_urls', true))),
            'gallery_urls' => implode('|', $this->parse_multiline_urls((string) get_post_meta($post_id, 'eep_gallery_urls', true))),
            'sessions' => (string) get_post_meta($post_id, 'eep_sessions', true),
            'key_speakers' => (string) get_post_meta($post_id, 'eep_key_speakers', true),
            'event_type' => implode('|', wp_get_post_terms($post_id, self::TAX_TYPE, ['fields' => 'names'])),
            'event_mode' => implode('|', wp_get_post_terms($post_id, self::TAX_MODE, ['fields' => 'names'])),
            'event_price_range' => implode('|', wp_get_post_terms($post_id, self::TAX_PRICE, ['fields' => 'names'])),
            'event_tags' => implode('|', wp_get_post_terms($post_id, self::TAX_TAG, ['fields' => 'names'])),
            'status' => (string) get_post_status($post_id),
        ];

        $headers = array_keys($this->get_import_supported_fields());
        $normalized = [];
        foreach ($headers as $header) {
            $normalized[$header] = isset($row[$header]) ? (string) $row[$header] : '';
        }
        return $normalized;
    }


    private function get_demo_export_rows() {
        return [[
            'title' => 'Global Robotics Summit 2026',
            'description' => 'A premier gathering of robotics engineers and researchers from around the world.',
            'excerpt' => 'Robotics conference with web app, itinerary, and live engagement features.',
            'start' => '2026-05-15 09:00:00',
            'end' => '2026-05-17 17:00:00',
            'timezone' => 'Europe/London',
            'venue' => 'ExCeL London',
            'address' => 'Royal Victoria Dock, 1 Western Gateway',
            'city' => 'London',
            'country' => 'United Kingdom',
            'lat' => '51.5085',
            'lng' => '0.0298',
            'capacity' => '500',
            'price' => '£299',
            'external_url' => 'https://example.com/robotics-summit',
            'contact_email' => 'info@robotics-summit.com',
            'contact_phone' => '+44 20 0000 0000',
            'contact_name' => 'Robotics Team',
            'cover_image_url' => 'https://example.com/images/robotics-hero.jpg',
            'supporting_docs_urls' => 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf|https://calibre-ebook.com/downloads/demos/demo.docx',
            'official_event' => 'no',
            'recurrence' => 'none',
            'recurrence_until' => '',
            'ticket_sales_enabled' => '1',
            'ticket_price' => '299.00',
            'ticket_button_label' => 'Get Tickets',
            'stream_url' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
            'video_urls' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
            'gallery_urls' => 'https://example.com/images/robotics-1.jpg|https://example.com/images/robotics-2.jpg',
            'sessions' => '[{"title":"Opening Session (Day 1)","start":"2026-05-15 09:00:00","end":"2026-05-15 10:30:00","location":"ExCeL London","speaker":"Dr Jane Smith","day":"May 15, 2026","notes":"Arrive early for badge check-in and seating.","attachments":["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf","dQw4w9WgXcQ"],"session_key":"demo-day1-opening"},{"title":"Technical Deep Dive (Day 1)","start":"2026-05-15 11:00:00","end":"2026-05-15 13:00:00","location":"ExCeL London - Hall B","speaker":"Marcus Webb","day":"May 15, 2026","notes":"Interactive workshop with practical examples.","attachments":["https://calibre-ebook.com/downloads/demos/demo.docx"],"session_key":"demo-day1-deep-dive"},{"title":"Q&A and Networking (Day 2)","start":"2026-05-16 14:00:00","end":"2026-05-16 15:15:00","location":"ExCeL London - Lounge","speaker":"Elena Rodriguez","day":"May 16, 2026","notes":"Vote questions in the app before this session.","attachments":["https://example.com/images/robotics-2.jpg"],"session_key":"demo-day2-qa"},{"title":"Closing Session (Day 3)","start":"2026-05-17 15:30:00","end":"2026-05-17 17:00:00","location":"ExCeL London","speaker":"Priya Patel","day":"May 17, 2026","notes":"Summary, next steps, and downloadable resource links.","attachments":["https://example.com/images/robotics-1.jpg"],"session_key":"demo-day3-closing"}]',
            'key_speakers' => '[{"name":"Dr Jane Smith","profession":"Robotics Engineer","subject":"Humanoid Systems","image":"https://example.com/images/speaker-jane-smith.jpg","details":"Lead researcher with 15+ years in autonomous robotics."},{"name":"Marcus Webb","profession":"Cloud Architect","subject":"Scalable Systems","image":"https://example.com/images/speaker-marcus-webb.jpg","details":"Focuses on high-scale event infrastructure."},{"name":"Elena Rodriguez","profession":"Design Director","subject":"Applied AI UX","image":"https://example.com/images/speaker-elena-rodriguez.jpg","details":"Leads design systems and attendee journey strategy."}]',
            'event_type' => 'Conference',
            'event_mode' => 'In-person',
            'event_price_range' => 'Premium',
            'event_tags' => 'AI|Manufacturing|Robotics',
            'status' => 'pending',
        ]];
    }


    private function stream_export_payload($format, $rows, $filename_prefix = 'events-export') {
        $format = sanitize_key((string) $format);
        if (!in_array($format, ['csv', 'json'], true)) {
            $format = 'csv';
        }
        $rows = is_array($rows) ? $rows : [];
        $headers = array_keys($this->get_import_supported_fields());

        nocache_headers();
        if ($format === 'json') {
            header('Content-Type: application/json; charset=utf-8');
            header('Content-Disposition: attachment; filename="' . sanitize_file_name($filename_prefix . '.json') . '"');
            echo wp_json_encode($rows, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            exit;
        }

        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename="' . sanitize_file_name($filename_prefix . '.csv') . '"');
        $output = fopen('php://output', 'w');
        if (!$output) {
            exit;
        }
        fputcsv($output, $headers);
        foreach ($rows as $row) {
            $line = [];
            foreach ($headers as $header) {
                $line[] = isset($row[$header]) ? (string) $row[$header] : '';
            }
            fputcsv($output, $line);
        }
        fclose($output);
        exit;
    }


    private function stream_tabular_export_payload($format, $rows, $filename_prefix = 'analytics-export', $json_payload = []) {
        $format = sanitize_key((string) $format);
        if (!in_array($format, ['csv', 'json'], true)) {
            $format = 'csv';
        }
        $rows = is_array($rows) ? $rows : [];

        nocache_headers();
        if ($format === 'json') {
            header('Content-Type: application/json; charset=utf-8');
            header('Content-Disposition: attachment; filename="' . sanitize_file_name($filename_prefix . '.json') . '"');
            $payload = !empty($json_payload) && is_array($json_payload) ? $json_payload : $rows;
            echo wp_json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            exit;
        }

        $headers = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            foreach (array_keys($row) as $key) {
                $key = (string) $key;
                if ($key !== '' && !in_array($key, $headers, true)) {
                    $headers[] = $key;
                }
            }
        }
        if (empty($headers)) {
            $headers = ['metric', 'value'];
            $rows = [['metric' => 'empty', 'value' => 'No data']];
        }

        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename="' . sanitize_file_name($filename_prefix . '.csv') . '"');
        $output = fopen('php://output', 'w');
        if (!$output) {
            exit;
        }
        fputcsv($output, $headers);
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $line = [];
            foreach ($headers as $header_key) {
                $line[] = isset($row[$header_key]) ? (string) $row[$header_key] : '';
            }
            fputcsv($output, $line);
        }
        fclose($output);
        exit;
    }


    private function build_owner_analytics_export_rows(array $report = []) {
        $rows = [];
        $totals = is_array($report['totals'] ?? null) ? $report['totals'] : [];
        $metric_labels = [
            'listing_views' => __('Listing Views', 'nexdirectory'),
            'click_phone' => __('Clicks to Phone', 'nexdirectory'),
            'click_website' => __('Clicks to Website', 'nexdirectory'),
            'click_whatsapp' => __('WhatsApp Clicks', 'nexdirectory'),
            'enquiries_received' => __('Enquiries Received', 'nexdirectory'),
            'map_impressions' => __('Map Impressions', 'nexdirectory'),
            'search_appearances' => __('Search Appearances', 'nexdirectory'),
            'conversion_rate' => __('View to Enquiry Conversion %', 'nexdirectory'),
        ];
        foreach ($metric_labels as $key => $label) {
            $value = $totals[$key] ?? 0;
            $rows[] = [
                'section' => 'summary',
                'metric' => $label,
                'value' => is_numeric($value) ? (string) $value : (string) sanitize_text_field((string) $value),
            ];
        }

        $event_rows = is_array($report['rows'] ?? null) ? $report['rows'] : [];
        foreach ($event_rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $rows[] = [
                'section' => 'event',
                'event_id' => (int) ($row['event_id'] ?? 0),
                'event_title' => sanitize_text_field((string) ($row['title'] ?? '')),
                'event_url' => esc_url_raw((string) ($row['url'] ?? '')),
                'listing_views' => (int) ($row['listing_views'] ?? 0),
                'click_phone' => (int) ($row['click_phone'] ?? 0),
                'click_website' => (int) ($row['click_website'] ?? 0),
                'click_whatsapp' => (int) ($row['click_whatsapp'] ?? 0),
                'enquiries_received' => (int) ($row['enquiries_received'] ?? 0),
                'map_impressions' => (int) ($row['map_impressions'] ?? 0),
                'search_appearances' => (int) ($row['search_appearances'] ?? 0),
                'conversion_rate' => (float) ($row['conversion_rate'] ?? 0),
            ];
        }
        return $rows;
    }


    private function build_platform_analytics_export_rows(array $report = []) {
        $rows = [];
        $summary = is_array($report['summary'] ?? null) ? $report['summary'] : [];
        foreach ($summary as $metric => $value) {
            $rows[] = [
                'section' => 'summary',
                'metric' => sanitize_text_field((string) $metric),
                'value' => is_scalar($value) ? (string) $value : wp_json_encode($value),
            ];
        }

        foreach ((array) ($report['growth'] ?? []) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $rows[] = [
                'section' => 'growth',
                'month' => sanitize_text_field((string) ($row['label'] ?? $row['bucket'] ?? '')),
                'listings' => (int) ($row['listings'] ?? 0),
                'users' => (int) ($row['users'] ?? 0),
            ];
        }
        foreach ((array) ($report['most_viewed_listings'] ?? []) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $rows[] = [
                'section' => 'most_viewed',
                'event_id' => (int) ($row['event_id'] ?? 0),
                'title' => sanitize_text_field((string) ($row['title'] ?? '')),
                'url' => esc_url_raw((string) ($row['url'] ?? '')),
                'total' => (int) ($row['total'] ?? 0),
            ];
        }
        foreach ((array) ($report['most_searched_categories'] ?? []) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $rows[] = [
                'section' => 'most_searched_categories',
                'category' => sanitize_text_field((string) ($row['label'] ?? $row['slug'] ?? '')),
                'slug' => sanitize_key((string) ($row['slug'] ?? '')),
                'total' => (int) ($row['total'] ?? 0),
            ];
        }
        foreach ((array) ($report['most_active_areas'] ?? []) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $rows[] = [
                'section' => 'most_active_areas',
                'area' => sanitize_text_field((string) ($row['bucket'] ?? '')),
                'total' => (int) ($row['total'] ?? 0),
            ];
        }
        foreach ((array) ($report['top_performing_businesses'] ?? []) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $rows[] = [
                'section' => 'top_performing_businesses',
                'owner_id' => (int) ($row['owner_id'] ?? 0),
                'owner_name' => sanitize_text_field((string) ($row['owner_name'] ?? '')),
                'listing_count' => (int) ($row['listing_count'] ?? 0),
                'listing_views' => (int) ($row['listing_views'] ?? 0),
                'enquiries_received' => (int) ($row['enquiries_received'] ?? 0),
                'click_phone' => (int) ($row['click_phone'] ?? 0),
                'click_website' => (int) ($row['click_website'] ?? 0),
                'click_whatsapp' => (int) ($row['click_whatsapp'] ?? 0),
                'conversion_rate' => (float) ($row['conversion_rate'] ?? 0),
            ];
        }

        return $rows;
    }


    public function handle_export_owner_analytics() {
        if (function_exists('epw_fs') && !epw_fs()->can_use_premium_code__premium_only()) {
            wp_die(__('Owner analytics export is a premium feature.', 'nexdirectory'));
        }
        if (!is_user_logged_in()) {
            wp_die(__('Login required.', 'nexdirectory'));
        }
        if (!isset($_POST['eep_export_owner_analytics_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_export_owner_analytics_nonce'])), 'eep_export_owner_analytics_nonce')) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }
        if (!$this->user_can_submit_events(get_current_user_id()) && !current_user_can('manage_options')) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }
        $format = sanitize_key((string) wp_unslash($_POST['eep_export_format'] ?? 'csv'));
        $days = isset($_POST['eep_owner_days']) ? absint((int) wp_unslash($_POST['eep_owner_days'])) : 30;
        if (!in_array($days, [7, 30, 90, 365], true)) {
            $days = 30;
        }
        $owner_id = get_current_user_id();
        $report = $this->get_owner_analytics_report($owner_id, $days);
        $rows = $this->build_owner_analytics_export_rows($report);
        $this->stream_tabular_export_payload($format, $rows, 'owner-analytics-' . gmdate('Ymd-His'), $report);
    }


    public function handle_export_platform_analytics() {
        if (function_exists('epw_fs') && !epw_fs()->can_use_premium_code__premium_only()) {
            wp_die(__('Platform analytics export is a premium feature.', 'nexdirectory'));
        }
        if (!current_user_can('manage_options')) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }
        if (!isset($_POST['eep_export_platform_analytics_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_export_platform_analytics_nonce'])), 'eep_export_platform_analytics_nonce')) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }
        $format = sanitize_key((string) wp_unslash($_POST['eep_export_format'] ?? 'csv'));
        $days = isset($_POST['eep_days']) ? absint((int) wp_unslash($_POST['eep_days'])) : 30;
        if (!in_array($days, [7, 30, 90, 365], true)) {
            $days = 30;
        }
        $report = $this->get_platform_analytics_report($days);
        $rows = $this->build_platform_analytics_export_rows($report);
        $this->stream_tabular_export_payload($format, $rows, 'platform-analytics-' . gmdate('Ymd-His'), $report);
    }


    public function handle_export_events() {
        if (!current_user_can('manage_options')) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }
        if (!isset($_POST['eep_export_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_export_nonce'])), 'eep_export_events_nonce')) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }

        $format = sanitize_key((string) wp_unslash($_POST['eep_export_format'] ?? 'csv'));
        $post_statuses = ['publish', 'pending', 'draft'];
        $ids = get_posts([
            'post_type' => self::POST_TYPE,
            'post_status' => $post_statuses,
            'posts_per_page' => -1,
            'fields' => 'ids',
            'orderby' => 'date',
            'order' => 'DESC',
        ]);

        $rows = [];
        foreach ((array) $ids as $post_id) {
            $row = $this->build_event_export_row((int) $post_id);
            if (!empty($row)) {
                $rows[] = $row;
            }
        }

        $this->stream_export_payload($format, $rows, 'event-events-export-' . gmdate('Ymd-His'));
    }


    public function handle_export_demo_events() {
        if (!current_user_can('manage_options')) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }
        if (!isset($_POST['eep_export_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_export_nonce'])), 'eep_export_events_nonce')) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }
        $format = sanitize_key((string) wp_unslash($_POST['eep_export_format'] ?? 'csv'));
        $this->stream_export_payload($format, $this->get_demo_export_rows(), 'event-events-demo-template');
    }


    public function handle_email_verification() {
        if (empty($_GET['eep_verify_email']) || empty($_GET['token'])) {
            return;
        }
        if ((string) get_option('eep_require_email_verification', '1') !== '1') {
            $this->redirect_with_message(home_url('/'), __('Email verification is not required.', 'nexdirectory'));
        }
        $user_id = absint($_GET['eep_verify_email']);
        $token = eep_sanitize_text(wp_unslash($_GET['token']));
        if (!$user_id || !$token) {
            return;
        }
        $stored = (string) get_user_meta($user_id, 'eep_email_verify_token', true);
        $expires = (int) get_user_meta($user_id, 'eep_email_verify_expires', true);
        if ($stored === '' || !hash_equals($stored, $token) || ($expires > 0 && time() > $expires)) {
            $this->redirect_with_message(home_url('/'), __('Verification link invalid or expired.', 'nexdirectory'), 'error');
        }
        update_user_meta($user_id, 'eep_email_verified', '1');
        delete_user_meta($user_id, 'eep_email_verify_token');
        delete_user_meta($user_id, 'eep_email_verify_expires');
        delete_user_meta($user_id, 'eep_email_verify_last_sent');
        $platform_mode = sanitize_key((string) get_option('eep_platform_mode', 'user_generated'));
        $this->redirect_with_message(
            home_url('/'),
            $platform_mode === 'single_organisation'
                ? __('Email verified successfully. Your account is now active.', 'nexdirectory')
                : __('Email verified. You can now submit your first event.', 'nexdirectory')
        );
    }


    private function process_rsvp_action($post_id, $intent = 'join', $user_id = 0) {
        $post_id = absint((int) $post_id);
        $intent = sanitize_key((string) $intent);
        if ($intent === '') {
            $intent = 'join';
        }
        $user_id = absint((int) $user_id);
        if ($user_id <= 0) {
            $user_id = get_current_user_id();
        }
        $profile_key = $this->resolve_profile_key_for_post($post_id);
        $ui_labels = $this->get_site_profile_ui_labels($profile_key);
        $is_events_profile = (string) ($ui_labels['is_events_profile'] ?? '1') === '1';
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $singular_lower = function_exists('mb_strtolower') ? mb_strtolower($singular_label, 'UTF-8') : strtolower($singular_label);
        $join_button_label = sanitize_text_field((string) ($ui_labels['single_rsvp_join'] ?? ($is_events_profile ? __('RSVP / Join Waitlist', 'nexdirectory') : __('Save Listing', 'nexdirectory'))));
        $leave_button_label = sanitize_text_field((string) ($ui_labels['single_rsvp_cancel'] ?? ($is_events_profile ? __('Cancel RSVP', 'nexdirectory') : __('Remove Saved Listing', 'nexdirectory'))));

        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return new WP_Error('eep_rsvp_invalid_event', $is_events_profile ? __('Invalid event.', 'nexdirectory') : sprintf(__('Invalid %s.', 'nexdirectory'), $singular_lower));
        }
        if ($user_id <= 0) {
            return new WP_Error('eep_rsvp_login_required', $is_events_profile ? __('Please log in to RSVP.', 'nexdirectory') : sprintf(__('Please log in to save this %s.', 'nexdirectory'), $singular_lower));
        }
        $post_status = (string) get_post_status($post_id);
        $owner_id = (int) get_post_field('post_author', $post_id);
        if ($post_status !== 'publish' && $user_id !== $owner_id && !user_can($user_id, 'edit_post', $post_id)) {
            return new WP_Error('eep_rsvp_private_event', $is_events_profile ? __('This event is not available for RSVP.', 'nexdirectory') : sprintf(__('This %s is not available right now.', 'nexdirectory'), $singular_lower));
        }

        global $wpdb;
        $table = $this->get_rsvp_table_name();
        $capacity = (int) get_post_meta($post_id, 'eep_capacity', true);
        if (!$is_events_profile) {
            $capacity = 0;
        }
        $now = current_time('mysql', 1);
        $existing_status = $wpdb->get_var($wpdb->prepare(
            "SELECT status FROM {$table} WHERE post_id = %d AND user_id = %d LIMIT 1",
            $post_id,
            $user_id
        ));
        if (!empty($wpdb->last_error)) {
            $this->append_runtime_log('error', 'RSVP status query failed', [
                'post_id' => (string) $post_id,
                'user_id' => (string) $user_id,
                'error' => (string) $wpdb->last_error,
            ]);
            return new WP_Error('eep_rsvp_query_failed', $is_events_profile ? __('RSVP request failed. Please retry.', 'nexdirectory') : __('Save request failed. Please retry.', 'nexdirectory'));
        }
        $has_existing_row = $existing_status !== null && $existing_status !== '';

        if ($intent !== 'join' || $has_existing_row) {
            $deleted = $wpdb->delete($table, [
                'post_id' => $post_id,
                'user_id' => $user_id,
            ], ['%d', '%d']);
            if ($deleted === false) {
                $this->append_runtime_log('error', 'RSVP removal failed', [
                    'post_id' => (string) $post_id,
                    'user_id' => (string) $user_id,
                    'error' => (string) $wpdb->last_error,
                ]);
                return new WP_Error('eep_rsvp_remove_failed', $is_events_profile ? __('RSVP removal failed. Please retry.', 'nexdirectory') : __('Saved-listing removal failed. Please retry.', 'nexdirectory'));
            }
            $this->sync_rsvp_meta_from_table($post_id);
            clean_post_cache($post_id);

            return [
                'result' => 'removed',
                'status' => 'none',
                'message' => $is_events_profile ? __('RSVP removed.', 'nexdirectory') : __('Listing removed from saved items.', 'nexdirectory'),
                'post_id' => $post_id,
                'rsvp_count' => count($this->get_event_attendees_by_status($post_id, 'yes')),
                'waitlist_count' => count($this->get_event_attendees_by_status($post_id, 'waitlist')),
                'next_intent' => 'join',
                'button_label' => $join_button_label,
            ];
        }

        $lock_name = 'eep_rsvp_' . $post_id;
        $locked = (int) $wpdb->get_var($wpdb->prepare('SELECT GET_LOCK(%s, 5)', $lock_name));
        if ($locked !== 1) {
            $this->append_runtime_log('warning', 'RSVP lock acquisition failed', [
                'post_id' => (string) $post_id,
                'user_id' => (string) $user_id,
            ]);
            return new WP_Error('eep_rsvp_busy', $is_events_profile ? __('RSVP is busy. Please retry.', 'nexdirectory') : __('Save action is busy. Please retry.', 'nexdirectory'));
        }
        $current_yes = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table} WHERE post_id = %d AND status = 'yes'",
            $post_id
        ));
        $new_status = ($capacity > 0 && $current_yes >= $capacity) ? 'waitlist' : 'yes';
        $result = $wpdb->replace($table, [
            'post_id' => $post_id,
            'user_id' => $user_id,
            'status' => $new_status,
            'created_at' => $now,
            'updated_at' => $now,
        ], ['%d', '%d', '%s', '%s', '%s']);
        if ($result === false) {
            $this->append_runtime_log('error', 'RSVP save failed', [
                'post_id' => (string) $post_id,
                'user_id' => (string) $user_id,
                'status' => $new_status,
                'error' => (string) $wpdb->last_error,
            ]);
            $wpdb->get_var($wpdb->prepare('SELECT RELEASE_LOCK(%s)', $lock_name));
            return new WP_Error('eep_rsvp_save_failed', $is_events_profile ? __('RSVP could not be saved. Please retry.', 'nexdirectory') : __('Listing could not be saved. Please retry.', 'nexdirectory'));
        }
        $wpdb->get_var($wpdb->prepare('SELECT RELEASE_LOCK(%s)', $lock_name));
        $this->sync_rsvp_meta_from_table($post_id);
        clean_post_cache($post_id);

        $message = $new_status === 'waitlist'
            ? __('Capacity reached. You were added to waitlist.', 'nexdirectory')
            : ($is_events_profile ? __('RSVP confirmed.', 'nexdirectory') : __('Listing saved.', 'nexdirectory'));

        return [
            'result' => 'joined',
            'status' => $new_status,
            'message' => $message,
            'post_id' => $post_id,
            'rsvp_count' => count($this->get_event_attendees_by_status($post_id, 'yes')),
            'waitlist_count' => count($this->get_event_attendees_by_status($post_id, 'waitlist')),
            'next_intent' => 'leave',
            'button_label' => $leave_button_label,
        ];
    }


    public function handle_rsvp_event() {
        $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0;
        $intent = sanitize_key((string) wp_unslash($_POST['intent'] ?? 'join'));
        $profile_key = $this->resolve_profile_key_for_post($post_id);
        $ui_labels = $this->get_site_profile_ui_labels($profile_key);
        $is_events_profile = (string) ($ui_labels['is_events_profile'] ?? '1') === '1';
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $singular_lower = function_exists('mb_strtolower') ? mb_strtolower($singular_label, 'UTF-8') : strtolower($singular_label);
        if (!is_user_logged_in()) {
            wp_die($is_events_profile ? __('Please log in to RSVP.', 'nexdirectory') : sprintf(__('Please log in to save this %s.', 'nexdirectory'), $singular_lower));
        }
        if (!$this->user_can_access_platform(get_current_user_id())) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }
        if (!$post_id || get_post_type($post_id) !== self::POST_TYPE) {
            wp_die($is_events_profile ? __('Invalid event.', 'nexdirectory') : sprintf(__('Invalid %s.', 'nexdirectory'), $singular_lower));
        }
        if (!isset($_POST['eep_rsvp_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_rsvp_nonce'])), 'eep_rsvp_' . $post_id)) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }

        $redirect = wp_get_referer() ?: get_permalink($post_id);
        $result = $this->process_rsvp_action($post_id, $intent, get_current_user_id());
        if (is_wp_error($result)) {
            $this->redirect_with_message($redirect, $result->get_error_message(), 'error');
        }
        $this->redirect_with_message(
            $redirect,
            (string) ($result['message'] ?? ($is_events_profile ? __('RSVP updated.', 'nexdirectory') : sprintf(__('%s updated.', 'nexdirectory'), $singular_label)))
        );
    }


    public function handle_report_event() {
        $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')));
        $singular_lower = function_exists('mb_strtolower') ? mb_strtolower($singular_label, 'UTF-8') : strtolower($singular_label);
        if ((string) get_option('eep_enable_event_reporting', '1') !== '1') {
            wp_die(sprintf(__('%s reporting is disabled.', 'nexdirectory'), $singular_label));
        }
        if (!is_user_logged_in()) {
            wp_die(sprintf(__('Please log in to report %s.', 'nexdirectory'), $singular_lower));
        }
        if (!$this->user_can_access_platform(get_current_user_id())) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }
        $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0;
        $reason = eep_sanitize_textarea(wp_unslash($_POST['reason'] ?? ''));
        if (!$post_id || !$reason || get_post_type($post_id) !== self::POST_TYPE) {
            wp_die(__('Invalid report.', 'nexdirectory'));
        }
        $post_status = (string) get_post_status($post_id);
        $owner_id = (int) get_post_field('post_author', $post_id);
        if ($post_status !== 'publish' && get_current_user_id() !== $owner_id && !current_user_can('edit_post', $post_id)) {
            wp_die(__('Invalid report.', 'nexdirectory'));
        }
        if (!isset($_POST['eep_report_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_report_nonce'])), 'eep_report_' . $post_id)) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }

        wp_insert_comment([
            'comment_post_ID' => $post_id,
            'comment_content' => $reason,
            'comment_type' => 'eep_report',
            'user_id' => get_current_user_id(),
            'comment_author' => wp_get_current_user()->display_name,
            'comment_author_email' => wp_get_current_user()->user_email,
            'comment_approved' => 1,
        ]);

        $admin_email = sanitize_email((string) get_option('admin_email'));
        if (is_email($admin_email)) {
            $subject = sprintf(__('%s report submitted', 'nexdirectory'), $singular_label);
            $moderation_url = admin_url('admin.php?page=eep-moderation');
            $body = sprintf(
                "%s: %s\nReason: %s\nLink: %s\nModeration Queue: %s",
                $singular_label,
                get_the_title($post_id),
                $reason,
                get_permalink($post_id),
                $moderation_url
            );

            $sent = (bool) $this->send_platform_email($admin_email, $subject, '', [
                'fallback_text' => $body,
            ]);
            if (!$sent) {
                $sent = (bool) wp_mail($admin_email, $subject, $body);
            }
            $this->append_runtime_log($sent ? 'info' : 'warning', 'Admin report notification email ' . ($sent ? 'sent' : 'failed'), [
                'post_id' => (string) $post_id,
                'admin_email' => (string) $admin_email,
            ]);
        }

        $this->redirect_with_message(
            wp_get_referer() ?: get_permalink($post_id),
            sprintf(
                __('Thanks for flagging this %s. Our moderation team will review it shortly.', 'nexdirectory'),
                $singular_lower
            )
        );
    }


    public function handle_contact_holder() {
        $ui_labels = method_exists($this, 'get_site_profile_ui_labels') ? (array) $this->get_site_profile_ui_labels() : [];
        $is_job_profile = (string) ($ui_labels['is_job_profile'] ?? '0') === '1';
        $is_events_profile = (string) ($ui_labels['is_events_profile'] ?? '1') === '1';
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Event', 'nexdirectory')));
        $singular_lower = function_exists('mb_strtolower') ? mb_strtolower($singular_label, 'UTF-8') : strtolower($singular_label);
        if (!is_user_logged_in()) {
            wp_die(__('Please log in to contact organizers.', 'nexdirectory'));
        }
        if (!$this->user_can_access_platform(get_current_user_id())) {
            wp_die(__('Not allowed.', 'nexdirectory'));
        }
        $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0;
        if (!$post_id || get_post_type($post_id) !== self::POST_TYPE) {
            wp_die(sprintf(__('Invalid %s.', 'nexdirectory'), $singular_lower));
        }
        $post_status = (string) get_post_status($post_id);
        $owner_id = (int) get_post_field('post_author', $post_id);
        if ($post_status !== 'publish' && get_current_user_id() !== $owner_id && !current_user_can('edit_post', $post_id)) {
            wp_die(sprintf(__('Invalid %s.', 'nexdirectory'), $singular_lower));
        }
        if ($is_events_profile && $owner_id > 0 && !$this->user_allows_event_messages($owner_id)) {
            $this->redirect_with_message(
                wp_get_referer() ?: get_permalink($post_id),
                __('Messaging is disabled for this event.', 'nexdirectory'),
                'error'
            );
        }
        if (!isset($_POST['eep_contact_nonce']) || !wp_verify_nonce(eep_sanitize_text(wp_unslash($_POST['eep_contact_nonce'])), 'eep_contact_holder_' . $post_id)) {
            wp_die(__('Invalid request.', 'nexdirectory'));
        }
        $recaptcha_token = sanitize_text_field((string) wp_unslash($_POST['eep_recaptcha_token'] ?? ''));
        if ($recaptcha_token !== '' && !$this->verify_recaptcha_token($recaptcha_token)) {
            $this->redirect_with_message(wp_get_referer() ?: get_permalink($post_id), __('reCAPTCHA validation failed.', 'nexdirectory'), 'error');
        }

        $subject = eep_sanitize_text(wp_unslash($_POST['subject'] ?? ''));
        $message = eep_sanitize_textarea(wp_unslash($_POST['message'] ?? ''));

        $uploaded_attachment_path = '';
        $uploaded_attachment_url = '';
        $has_cv_upload = isset($_FILES['application_cv']) && is_array($_FILES['application_cv']) && ((int) ($_FILES['application_cv']['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_NO_FILE);
        if ($is_job_profile && $has_cv_upload) {
            $upload_error = (int) ($_FILES['application_cv']['error'] ?? UPLOAD_ERR_NO_FILE);
            if ($upload_error !== UPLOAD_ERR_OK) {
                $this->redirect_with_message(wp_get_referer() ?: get_permalink($post_id), __('CV upload failed. Please try again.', 'nexdirectory'), 'error');
            }
            $max_kb = max(64, min(20480, absint((int) get_option('eep_upload_file_max_kb', 2048))));
            $max_bytes = $max_kb * 1024;
            $file_size = absint((int) ($_FILES['application_cv']['size'] ?? 0));
            if ($file_size <= 0 || $file_size > $max_bytes) {
                $this->redirect_with_message(
                    wp_get_referer() ?: get_permalink($post_id),
                    sprintf(__('CV must be a PDF up to %d KB.', 'nexdirectory'), $max_kb),
                    'error'
                );
            }
            $raw_file_name = sanitize_file_name((string) ($_FILES['application_cv']['name'] ?? 'cv.pdf'));
            $check = wp_check_filetype_and_ext((string) ($_FILES['application_cv']['tmp_name'] ?? ''), $raw_file_name, ['pdf' => 'application/pdf']);
            $ext = strtolower((string) ($check['ext'] ?? ''));
            if ($ext !== 'pdf') {
                $this->redirect_with_message(wp_get_referer() ?: get_permalink($post_id), __('Only PDF CV uploads are allowed.', 'nexdirectory'), 'error');
            }
            if (!function_exists('wp_handle_upload')) {
                require_once ABSPATH . 'wp-admin/includes/file.php';
            }
            $upload_result = wp_handle_upload($_FILES['application_cv'], [
                'test_form' => false,
                'mimes' => ['pdf' => 'application/pdf'],
            ]);
            if (!is_array($upload_result) || !empty($upload_result['error']) || empty($upload_result['file'])) {
                $this->redirect_with_message(wp_get_referer() ?: get_permalink($post_id), __('CV upload could not be processed.', 'nexdirectory'), 'error');
            }
            $uploaded_attachment_path = (string) $upload_result['file'];
            $uploaded_attachment_url = esc_url_raw((string) ($upload_result['url'] ?? ''));
        }

        if ($is_job_profile) {
            if ($subject === '') {
                $subject = sprintf(__('Job application: %s', 'nexdirectory'), get_the_title($post_id));
            }
            if ($message === '' && $uploaded_attachment_url === '') {
                $this->redirect_with_message(wp_get_referer() ?: get_permalink($post_id), __('Please add a short message or upload your CV.', 'nexdirectory'), 'error');
            }
        } elseif ($subject === '' || $message === '') {
            $this->redirect_with_message(wp_get_referer() ?: get_permalink($post_id), __('Subject and message are required.', 'nexdirectory'), 'error');
        }

        $sender_id = (int) get_current_user_id();
        $author_id = (int) get_post_field('post_author', $post_id);
        $recipient_id = $author_id;
        $contact_target = sanitize_key((string) wp_unslash($_POST['eep_contact_target'] ?? 'owner'));
        $send_to_admin_inbox = $is_events_profile && $contact_target === 'admin';

        if ($send_to_admin_inbox) {
            $admin_recipient_id = 0;
            $admin_email = sanitize_email((string) get_option('admin_email'));
            if (is_email((string) $admin_email)) {
                $admin_user = get_user_by('email', (string) $admin_email);
                if ($admin_user instanceof WP_User) {
                    $admin_recipient_id = (int) $admin_user->ID;
                }
            }
            if ($admin_recipient_id <= 0 || $admin_recipient_id === $sender_id) {
                $admin_ids = get_users([
                    'role' => 'administrator',
                    'fields' => 'ID',
                    'orderby' => 'ID',
                    'order' => 'ASC',
                    'number' => 20,
                ]);
                if (is_array($admin_ids)) {
                    foreach ($admin_ids as $admin_id_candidate) {
                        $admin_id_candidate = absint((int) $admin_id_candidate);
                        if ($admin_id_candidate > 0 && $admin_id_candidate !== $sender_id) {
                            $admin_recipient_id = $admin_id_candidate;
                            break;
                        }
                    }
                }
            }
            if ($admin_recipient_id > 0) {
                $recipient_id = $admin_recipient_id;
            }
        }

        if ($recipient_id <= 0) {
            $this->redirect_with_message(wp_get_referer() ?: get_permalink($post_id), sprintf(__('No inbox recipient is configured for this %s.', 'nexdirectory'), $singular_lower), 'error');
        }
        if ($recipient_id === $sender_id && !$send_to_admin_inbox) {
            $this->redirect_with_message(wp_get_referer() ?: get_permalink($post_id), __('You cannot message your own listing.', 'nexdirectory'), 'error');
        }

        $message_body = $message;
        if ($is_job_profile) {
            $message_body = trim((string) $message_body) !== '' ? $message_body : __('No message provided. CV attached.', 'nexdirectory');
        }
        if ($uploaded_attachment_url !== '') {
            $message_body .= "\n\n" . sprintf(
                __('Attachment: %s', 'nexdirectory'),
                $uploaded_attachment_url
            );
        }

        $send_result = $this->inbox_send_message(
            $post_id,
            $sender_id,
            $recipient_id,
            $subject,
            $message_body,
            $uploaded_attachment_url
        );
        if (is_wp_error($send_result)) {
            if ($uploaded_attachment_path !== '' && file_exists($uploaded_attachment_path) && is_file($uploaded_attachment_path)) {
                @unlink($uploaded_attachment_path); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
            }
            $this->redirect_with_message(wp_get_referer() ?: get_permalink($post_id), __('Message could not be sent to inbox. Please try again.', 'nexdirectory'), 'error');
        }

        $thread_id = absint((int) ($send_result['thread_id'] ?? 0));
        $message_id = absint((int) ($send_result['message_id'] ?? 0));
        $inbox_only_delivery = (string) wp_unslash($_POST['eep_inbox_only'] ?? '0') === '1';
        if (!$inbox_only_delivery) {
            $this->send_inbox_message_notification_email(
                $recipient_id,
                $sender_id,
                $post_id,
                $thread_id,
                $message_id,
                $subject
            );
        }
        $this->record_analytics_event($post_id, 'enquiry_received', [
            'context' => $is_job_profile ? 'job_apply_form' : 'contact_form',
            'user_id' => get_current_user_id(),
        ]);

        $this->redirect_with_message(
            wp_get_referer() ?: get_permalink($post_id),
            $send_to_admin_inbox
                ? __('Message sent to admin inbox.', 'nexdirectory')
                : ($is_job_profile
                ? __('Application sent to the listing owner inbox.', 'nexdirectory')
                : sprintf(__('Message sent to this %s inbox.', 'nexdirectory'), $singular_lower))
        );
    }


    public function on_event_status_transition($new_status, $old_status, $post) {
        if (!$post) {
            return;
        }
        $post_type = sanitize_key((string) ($post->post_type ?? ''));
        if ($post_type === '') {
            return;
        }
        if ($post_type !== self::POST_TYPE) {
            $chatbot_post_types = method_exists($this, 'get_chatbot_selected_post_types')
                ? (array) $this->get_chatbot_selected_post_types()
                : [];
            if (!in_array($post_type, $chatbot_post_types, true)) {
                return;
            }
            $post_id = (int) $post->ID;
            if ($old_status === 'publish' && $new_status !== 'publish') {
                $this->delete_chatbot_embedding_for_event($post_id);
                return;
            }
            if ($new_status === 'publish') {
                $this->update_chatbot_embedding_for_event($post_id);
            }
            return;
        }
        if ($this->is_generating_recurrences) {
            return;
        }

        $post_id = (int) $post->ID;
        $this->maybe_submit_indexnow_for_post_transition((string) $new_status, (string) $old_status, $post);

        if ($old_status === 'pending' && $new_status !== 'pending') {
            delete_post_meta($post_id, 'eep_admin_pending_notified');
        }
        if ($new_status === 'pending' && $old_status !== 'pending') {
            $this->notify_admin_submission($post_id);
        }

        if ($old_status === 'publish' && $new_status !== 'publish') {
            $this->delete_chatbot_embedding_for_event($post_id);
            return;
        }

        if ($new_status !== 'publish') {
            return;
        }
        $this->update_chatbot_embedding_for_event($post_id);
        if ($old_status !== 'publish') {
            $this->maybe_confirm_requested_official_event($post_id);
            if (get_post_meta($post_id, 'eep_generated_from_parent', true)) {
                return;
            }
            $this->generate_recurrences($post_id);
        }
    }


    public function handle_indexnow_key_request($wp) {
        if (!is_object($wp) || !isset($wp->request)) {
            return;
        }
        $request_path = trim((string) $wp->request, '/');
        if ($request_path === '') {
            return;
        }
        $key = $this->normalize_indexnow_key((string) get_option('eep_indexnow_key', ''));
        if ($key === '') {
            return;
        }
        if (strcasecmp($request_path, $key . '.txt') !== 0) {
            return;
        }
        if (!headers_sent()) {
            status_header(200);
            nocache_headers();
            header('Content-Type: text/plain; charset=utf-8');
        }
        echo esc_html($key);
        exit;
    }


    private function normalize_indexnow_key($key) {
        $key = preg_replace('/[^A-Za-z0-9]/', '', (string) $key);
        $key = substr((string) $key, 0, 128);
        $length = strlen((string) $key);
        if ($length < 8 || $length > 128) {
            return '';
        }
        return $key;
    }


    private function generate_indexnow_key() {
        try {
            $bytes = random_bytes(20);
            $key = bin2hex($bytes);
        } catch (Exception $e) {
            $key = wp_generate_password(40, false, false);
        }
        $key = preg_replace('/[^A-Za-z0-9]/', '', (string) $key);
        if (strlen((string) $key) < 8) {
            $key .= substr(md5(uniqid((string) wp_rand(), true)), 0, 16);
        }
        return substr((string) $key, 0, 128);
    }


    private function ensure_indexnow_key() {
        $key = $this->normalize_indexnow_key((string) get_option('eep_indexnow_key', ''));
        if ($key !== '') {
            return $key;
        }
        $key = $this->generate_indexnow_key();
        if ($key !== '') {
            update_option('eep_indexnow_key', $key, false);
        }
        return $key;
    }


    private function maybe_submit_indexnow_for_post_transition($new_status, $old_status, $post) {
        if ((string) get_option('eep_indexnow_enabled', '0') !== '1') {
            return;
        }
        if (!$post || (int) ($post->ID ?? 0) <= 0) {
            return;
        }
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return;
        }
        $post_id = (int) $post->ID;
        if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
            return;
        }

        $submit_updates = (string) get_option('eep_indexnow_submit_updates', '0') === '1';
        $submit_removals = (string) get_option('eep_indexnow_submit_removals', '0') === '1';
        $should_submit = false;
        $reason = '';

        if ($new_status === 'publish' && $old_status !== 'publish') {
            $should_submit = true;
            $reason = 'publish_new';
        } elseif ($new_status === 'publish' && $old_status === 'publish' && $submit_updates) {
            $should_submit = true;
            $reason = 'publish_update';
        } elseif ($old_status === 'publish' && $new_status !== 'publish' && $submit_removals) {
            $should_submit = true;
            $reason = 'publish_removed';
        }

        if (!$should_submit) {
            return;
        }

        $url = get_permalink($post_id);
        if (!is_string($url) || trim($url) === '') {
            return;
        }
        $this->submit_urls_to_indexnow([$url], $reason);
    }


    private function submit_urls_to_indexnow(array $urls, $reason = 'manual') {
        $urls = array_values(array_unique(array_filter(array_map('esc_url_raw', array_map('strval', $urls)))));
        if (empty($urls)) {
            return false;
        }

        $key = $this->ensure_indexnow_key();
        if ($key === '') {
            $this->append_runtime_log('warning', 'IndexNow submit skipped: missing key', [
                'reason' => sanitize_key((string) $reason),
            ]);
            return false;
        }

        $endpoint = esc_url_raw((string) get_option('eep_indexnow_endpoint', 'https://api.indexnow.org/indexnow'));
        if ($endpoint === '') {
            $endpoint = 'https://api.indexnow.org/indexnow';
        }
        $host = wp_parse_url(home_url('/'), PHP_URL_HOST);
        if (!is_string($host) || $host === '') {
            $host = '';
        }
        $key_location = home_url('/' . rawurlencode($key) . '.txt');

        $payload = [
            'host' => $host,
            'key' => $key,
            'keyLocation' => esc_url_raw((string) $key_location),
            'urlList' => $urls,
        ];

        $response = wp_remote_post($endpoint, [
            'headers' => [
                'Content-Type' => 'application/json',
            ],
            'body' => wp_json_encode($payload),
            'timeout' => 12,
            'blocking' => true,
        ]);

        if (is_wp_error($response)) {
            $this->append_runtime_log('warning', 'IndexNow request failed', [
                'reason' => sanitize_key((string) $reason),
                'message' => $response->get_error_message(),
                'url_count' => (string) count($urls),
            ]);
            return false;
        }

        $status = (int) wp_remote_retrieve_response_code($response);
        $ok = in_array($status, [200, 202], true);
        $this->append_runtime_log($ok ? 'info' : 'warning', $ok ? 'IndexNow submit succeeded' : 'IndexNow submit failed', [
            'reason' => sanitize_key((string) $reason),
            'status' => (string) $status,
            'url_count' => (string) count($urls),
            'endpoint' => $endpoint,
        ]);
        return $ok;
    }


    private function generate_recurrences($post_id) {
        $recurrence = get_post_meta($post_id, 'eep_recurrence', true);
        $until = get_post_meta($post_id, 'eep_recurrence_until', true);
        $start = get_post_meta($post_id, 'eep_start', true);
        $end = get_post_meta($post_id, 'eep_end', true);
        if (!in_array($recurrence, ['daily', 'weekly', 'monthly'], true) || !$until || !$start) {
            return;
        }
        if (get_post_meta($post_id, 'eep_recurrence_generated', true) === '1') {
            return;
        }

        $start_ts = strtotime($start);
        $end_ts = $end ? strtotime($end) : false;
        $until_ts = strtotime($until . ' 23:59:59');
        if (!$start_ts || !$until_ts || $until_ts <= $start_ts) {
            return;
        }

        $step = '+1 day';
        if ($recurrence === 'weekly') {
            $step = '+1 week';
        } elseif ($recurrence === 'monthly') {
            $step = '+1 month';
        }

        $limit = 52;
        $count = 0;
        $current_start = $start_ts;
        $this->is_generating_recurrences = true;
        while ($count < $limit) {
            $current_start = strtotime($step, $current_start);
            if (!$current_start || $current_start > $until_ts) {
                break;
            }
            $author_id = (int) get_post_field('post_author', $post_id);
            if (!$this->can_create_more_events($author_id, 1)) {
                break;
            }
            $new_id = wp_insert_post([
                'post_type' => self::POST_TYPE,
                'post_status' => 'publish',
                'post_author' => $author_id,
                'post_title' => get_the_title($post_id),
                'post_content' => get_post_field('post_content', $post_id),
                'post_excerpt' => get_post_field('post_excerpt', $post_id),
            ]);
            if (!$new_id || is_wp_error($new_id)) {
                break;
            }

            foreach ($this->get_event_meta_fields() as $key => $type) {
                update_post_meta($new_id, $key, get_post_meta($post_id, $key, true));
            }
            update_post_meta($new_id, 'eep_generated_from_parent', (string) $post_id);
            update_post_meta($new_id, 'eep_recurrence', 'none');
            update_post_meta($new_id, 'eep_recurrence_until', '');
            update_post_meta($new_id, 'eep_start', gmdate('Y-m-d H:i:s', $current_start));
            if ($end_ts) {
                $duration = max(0, $end_ts - $start_ts);
                update_post_meta($new_id, 'eep_end', gmdate('Y-m-d H:i:s', $current_start + $duration));
            }
            wp_set_post_terms($new_id, wp_get_post_terms($post_id, self::TAX_TYPE, ['fields' => 'ids']), self::TAX_TYPE, false);
            wp_set_post_terms($new_id, wp_get_post_terms($post_id, self::TAX_MODE, ['fields' => 'ids']), self::TAX_MODE, false);

            $count++;
        }
        $this->is_generating_recurrences = false;

        update_post_meta($post_id, 'eep_recurrence_generated', '1');
    }


    public function send_event_reminders() {
        $now = time();
        $window_start = gmdate('Y-m-d H:i:s', $now + 23 * HOUR_IN_SECONDS);
        $window_end = gmdate('Y-m-d H:i:s', $now + 25 * HOUR_IN_SECONDS);
        $today = gmdate('Y-m-d');
        $query = new WP_Query([
            'post_type' => self::POST_TYPE,
            'post_status' => 'publish',
            'posts_per_page' => 100,
            'fields' => 'ids',
            'meta_query' => [
                [
                    'key' => 'eep_start',
                    'value' => [$window_start, $window_end],
                    'compare' => 'BETWEEN',
                    'type' => 'CHAR',
                ],
            ],
        ]);
        if (!$query->have_posts()) {
            return;
        }

        foreach ((array) $query->posts as $post_id) {
            $post_id = (int) $post_id;
            if (get_post_meta($post_id, 'eep_reminder_sent', true) === $today) {
                continue;
            }
            $attendees = $this->get_event_attendees_by_status($post_id, 'yes');
            $offset = 0;
            foreach ((array) $attendees as $user_id) {
                $user_id = (int) $user_id;
                if ($user_id <= 0) {
                    continue;
                }
                $hook_args = [$post_id, $user_id, $today];
                if ($this->should_use_action_scheduler()) {
                    if (function_exists('as_next_scheduled_action') && function_exists('as_schedule_single_action')) {
                        $next_action = as_next_scheduled_action('eep_send_single_reminder_email', $hook_args, 'eep');
                        if (!$next_action) {
                            as_schedule_single_action(time() + $offset, 'eep_send_single_reminder_email', $hook_args, 'eep');
                        }
                    }
                } elseif (!wp_next_scheduled('eep_send_single_reminder_email', $hook_args)) {
                    wp_schedule_single_event(time() + $offset, 'eep_send_single_reminder_email', $hook_args);
                }
                $offset += 5;
            }
            update_post_meta($post_id, 'eep_reminder_sent', $today);
        }
    }


    public function send_single_reminder_email($post_id, $user_id, $batch_day = '') {
        $post_id = absint($post_id);
        $user_id = absint($user_id);
        $batch_day = sanitize_text_field((string) $batch_day);
        if (!$post_id || !$user_id || get_post_status($post_id) !== 'publish') {
            return;
        }
        if (!$this->is_notification_toggle_enabled('eep_notify_author_reminder', '1')) {
            return;
        }

        $dedupe_key = 'eep_reminder_' . $post_id . '_' . $user_id . '_' . $batch_day;
        if (get_transient($dedupe_key)) {
            return;
        }

        $user = get_user_by('id', $user_id);
        if (!$user || !is_email($user->user_email)) {
            return;
        }

        $allow_event_template_override = (string) get_option('eep_enable_per_event_email_templates', '0') === '1';
        $subject_template = $allow_event_template_override ? (string) get_post_meta($post_id, 'eep_email_subject_reminder', true) : '';
        if ($subject_template === '') {
            $subject_template = (string) get_option('eep_email_subject_reminder', $this->get_default_event_email_template_value('eep_email_subject_reminder'));
        }
        $subject = trim((string) $this->render_event_email_subject($subject_template, $post_id, __('Reminder', 'nexdirectory')));
        if ($subject === '') {
            $subject = sprintf(__('Reminder: %s is tomorrow', 'nexdirectory'), get_the_title($post_id));
        }
        $body_template = $allow_event_template_override ? (string) get_post_meta($post_id, 'eep_email_body_reminder', true) : '';
        if ($body_template === '') {
            $body_template = (string) get_option('eep_email_body_reminder', $this->get_default_event_email_template_value('eep_email_body_reminder'));
        }
        $custom_body_html = $this->render_event_email_body($body_template, $post_id, __('Reminder', 'nexdirectory'), '');
        $fallback = sprintf(
            "Event: %s\nWhen: %s\nLink: %s",
            get_the_title($post_id),
            $this->format_event_date($post_id),
            get_permalink($post_id)
        );
        $this->send_platform_email($user->user_email, $subject, 'reminder.php', [
            'user' => $user,
            'post_id' => $post_id,
            'event_title' => get_the_title($post_id),
            'event_date' => $this->format_event_date($post_id),
            'event_url' => get_permalink($post_id),
            'custom_body_html' => $custom_body_html,
            'fallback_text' => $fallback,
        ]);
        set_transient($dedupe_key, '1', DAY_IN_SECONDS);
    }


    public function handle_event_webapp_pwa_asset_requests() {
        $manifest_flag = isset($_GET['eep_webapp_manifest']) ? sanitize_key((string) wp_unslash($_GET['eep_webapp_manifest'])) : '';
        $sw_flag = isset($_GET['eep_webapp_sw']) ? sanitize_key((string) wp_unslash($_GET['eep_webapp_sw'])) : '';
        $icon_flag = isset($_GET['eep_webapp_icon']) ? sanitize_key((string) wp_unslash($_GET['eep_webapp_icon'])) : '';
        $is_manifest_request = in_array($manifest_flag, ['1', 'true', 'yes', 'on'], true);
        $is_sw_request = in_array($sw_flag, ['1', 'true', 'yes', 'on'], true);
        $is_icon_request = in_array($icon_flag, ['1', 'true', 'yes', 'on'], true);
        if (!$is_manifest_request && !$is_sw_request && !$is_icon_request) {
            return;
        }

        $post_id = isset($_GET['event_id']) ? absint((int) wp_unslash($_GET['event_id'])) : 0;
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            status_header(404);
            exit;
        }

        $auth_identity = $this->resolve_event_webapp_auth_identity($post_id, null, true);
        $current_user_id = absint((int) ($auth_identity['user_id'] ?? 0));
        $can_manage = $this->event_webapp_user_can_manage_event($post_id, $current_user_id);
        $post_status = (string) get_post_status($post_id);
        if ($post_status !== 'publish' && !$can_manage) {
            status_header(403);
            exit;
        }

        $settings = $this->get_event_webapp_settings($post_id);
        if (empty($settings['enabled']) && !$can_manage) {
            status_header(403);
            exit;
        }
        if (!$this->event_webapp_user_can_access_event($post_id, $current_user_id, $settings, $can_manage)) {
            status_header($current_user_id <= 0 && !empty($settings['requires_login']) ? 401 : 403);
            exit;
        }

        $event_title = sanitize_text_field((string) get_the_title($post_id));
        if ($event_title === '') {
            $event_title = __('Event App', 'nexdirectory');
        }
        $accent_color = sanitize_hex_color((string) ($settings['accent_color'] ?? ''));
        if ($accent_color === null || $accent_color === '') {
            $accent_color = sanitize_hex_color((string) get_option('eep_style_accent_color', '#4F46E5')) ?: '#4F46E5';
        }
        $loading_bg_color = sanitize_hex_color((string) ($settings['loading_bg_color'] ?? ''));
        if ($loading_bg_color === null || $loading_bg_color === '') {
            $loading_bg_color = '#ffffff';
        }

        if ($is_icon_request) {
            $icon_url = esc_url_raw((string) ($settings['icon_url'] ?? ''));
            if ($icon_url === '') {
                $icon_url = esc_url_raw((string) $this->get_schema_site_logo_url());
            }
            if ($icon_url === '') {
                status_header(404);
                exit;
            }

            nocache_headers();
            header('Content-Type: image/svg+xml; charset=utf-8');
            echo '<?xml version="1.0" encoding="UTF-8"?>';
            echo '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-hidden="true">';
            echo '<rect width="512" height="512" fill="#ffffff"/>';
            echo '<image href="' . esc_url($icon_url) . '" x="64" y="64" width="384" height="384" preserveAspectRatio="xMidYMid meet"/>';
            echo '</svg>';
            exit;
        }

        if ($is_manifest_request) {
            $start_url = $this->append_event_webapp_install_query_args((string) $this->get_event_webapp_page_url($post_id), $auth_identity);
            $scope = (string) $this->get_event_webapp_service_worker_scope($post_id);
            $icon_url = esc_url_raw((string) ($settings['icon_url'] ?? ''));
            $splash_url = esc_url_raw((string) ($settings['splash_url'] ?? ''));
            $short_name = $event_title;
            if (function_exists('mb_substr')) {
                $short_name = (string) mb_substr($short_name, 0, 30);
            } else {
                $short_name = (string) substr($short_name, 0, 30);
            }

            $icons = [];
            if ($icon_url !== '') {
                $icon_asset_url = esc_url_raw((string) eep_add_query_arg([
                    'eep_webapp_icon' => '1',
                    'event_id' => $post_id,
                ], home_url('/')));
                $icon_asset_url = $this->append_event_webapp_install_query_args($icon_asset_url, $auth_identity);
                $icons[] = [
                    'src' => $icon_asset_url,
                    'type' => 'image/svg+xml',
                    'sizes' => '192x192',
                    'purpose' => 'any',
                ];
                $icons[] = [
                    'src' => $icon_asset_url,
                    'type' => 'image/svg+xml',
                    'sizes' => '512x512',
                    'purpose' => 'any',
                ];
            }

            $manifest = [
                'id' => 'eep-event-webapp-' . $post_id,
                'name' => $event_title,
                'short_name' => $short_name !== '' ? $short_name : $event_title,
                'start_url' => $start_url,
                'scope' => $scope,
                'display' => 'standalone',
                'orientation' => 'portrait',
                'theme_color' => $accent_color,
                'background_color' => $loading_bg_color,
                'description' => eep_sanitize_textarea((string) ($settings['subtitle'] ?? '')),
                'lang' => str_replace('_', '-', get_locale()),
            ];
            if (!empty($icons)) {
                $manifest['icons'] = $icons;
            }
            if ($splash_url !== '') {
                $manifest['screenshots'] = [[
                    'src' => $splash_url,
                    'sizes' => '1242x2688',
                    'type' => 'image/png',
                    'form_factor' => 'wide',
                ]];
            }

            nocache_headers();
            header('Content-Type: application/manifest+json; charset=utf-8');
            echo wp_json_encode($manifest, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            exit;
        }

        $start_url = $this->append_event_webapp_install_query_args((string) $this->get_event_webapp_page_url($post_id), $auth_identity);
        $manifest_url = $this->append_event_webapp_install_query_args((string) $this->get_event_webapp_manifest_url($post_id), $auth_identity);
        $permalink = esc_url_raw((string) get_permalink($post_id));
        $bootstrap_url = $this->append_event_webapp_install_query_args((string) eep_add_query_arg('event_id', $post_id, rest_url('eep/v1/event-webapp-bootstrap')), $auth_identity);
        $chat_url = $this->append_event_webapp_install_query_args((string) eep_add_query_arg('event_id', $post_id, rest_url('eep/v1/event-webapp-chat')), $auth_identity);
        $dm_url = $this->append_event_webapp_install_query_args((string) eep_add_query_arg('event_id', $post_id, rest_url('eep/v1/event-webapp-dm')), $auth_identity);
        $qa_url = $this->append_event_webapp_install_query_args((string) eep_add_query_arg('event_id', $post_id, rest_url('eep/v1/event-webapp-qa')), $auth_identity);
        $notifications_url = $this->append_event_webapp_install_query_args((string) eep_add_query_arg('event_id', $post_id, rest_url('eep/v1/event-webapp-notifications')), $auth_identity);
        $itinerary_url = $this->append_event_webapp_install_query_args((string) eep_add_query_arg('event_id', $post_id, rest_url('eep/v1/event-webapp-itinerary')), $auth_identity);
        $icon_asset_url = '';
        $icon_url = esc_url_raw((string) ($settings['icon_url'] ?? ''));
        if ($icon_url !== '') {
            $icon_asset_url = esc_url_raw((string) eep_add_query_arg([
                'eep_webapp_icon' => '1',
                'event_id' => $post_id,
            ], home_url('/')));
            $icon_asset_url = $this->append_event_webapp_install_query_args($icon_asset_url, $auth_identity);
        }

        $precache_urls = array_values(array_unique(array_filter([
            $start_url,
            $manifest_url,
            $permalink,
            $bootstrap_url,
            $chat_url,
            $dm_url,
            $qa_url,
            $notifications_url,
            $itinerary_url,
            $icon_asset_url !== '' ? $icon_asset_url : $icon_url,
            esc_url_raw((string) ($settings['splash_url'] ?? '')),
        ])));
        $cache_name = 'eep-event-webapp-' . $post_id . '-' . substr(md5((string) self::VERSION . '|' . $post_id . '|' . $accent_color), 0, 10);
        $sw_data = [
            'cacheName' => $cache_name,
            'precacheUrls' => $precache_urls,
            'offlineFallback' => $start_url,
        ];
        $sw_data_json = wp_json_encode($sw_data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        if (!is_string($sw_data_json) || $sw_data_json === '') {
            $sw_data_json = '{"cacheName":"","precacheUrls":[],"offlineFallback":"/"}';
        }

        nocache_headers();
        header('Content-Type: application/javascript; charset=utf-8');
        header('Service-Worker-Allowed: /');
        echo "/* Event Web App Service Worker */\n";
        echo 'const SW_DATA = ' . $sw_data_json . ";\n";
        echo "const CACHE_NAME = String(SW_DATA.cacheName || 'eep-event-webapp-cache');\n";
        echo "const PRECACHE_URLS = Array.isArray(SW_DATA.precacheUrls) ? SW_DATA.precacheUrls.filter(Boolean) : [];\n";
        echo "const OFFLINE_FALLBACK = String(SW_DATA.offlineFallback || '/');\n";
        echo "self.addEventListener('install', (event) => {\n";
        echo "  event.waitUntil(\n";
        echo "    caches.open(CACHE_NAME).then((cache) => Promise.all(PRECACHE_URLS.map((url) => cache.add(new Request(url, { credentials: 'same-origin' })).catch(() => null)))).then(() => self.skipWaiting())\n";
        echo "  );\n";
        echo "});\n";
        echo "self.addEventListener('activate', (event) => {\n";
        echo "  event.waitUntil(\n";
        echo "    caches.keys().then((keys) => Promise.all(keys.filter((key) => key.indexOf('eep-event-webapp-') === 0 && key !== CACHE_NAME).map((key) => caches.delete(key)))).then(() => self.clients.claim())\n";
        echo "  );\n";
        echo "});\n";
        echo "const networkFirst = async (request) => {\n";
        echo "  const cache = await caches.open(CACHE_NAME);\n";
        echo "  try {\n";
        echo "    const response = await fetch(request);\n";
        echo "    if (response && response.ok) {\n";
        echo "      cache.put(request, response.clone());\n";
        echo "    }\n";
        echo "    return response;\n";
        echo "  } catch (err) {\n";
        echo "    return (await cache.match(request)) || (request.mode === 'navigate' ? cache.match(OFFLINE_FALLBACK) : null);\n";
        echo "  }\n";
        echo "};\n";
        echo "self.addEventListener('fetch', (event) => {\n";
        echo "  if (!event.request || event.request.method !== 'GET') {\n";
        echo "    return;\n";
        echo "  }\n";
        echo "  const url = new URL(event.request.url);\n";
        echo "  const sameOrigin = url.origin === self.location.origin;\n";
        echo "  const isEventApi = url.pathname.indexOf('/wp-json/eep/v1/event-webapp-') !== -1;\n";
        echo "  if (isEventApi) {\n";
        echo "    event.respondWith(networkFirst(event.request));\n";
        echo "    return;\n";
        echo "  }\n";
        echo "  if (event.request.mode === 'navigate') {\n";
        echo "    event.respondWith(networkFirst(event.request));\n";
        echo "    return;\n";
        echo "  }\n";
        echo "  if (!sameOrigin) {\n";
        echo "    return;\n";
        echo "  }\n";
        echo "  const isStaticAsset = /\\.(?:css|js|png|jpe?g|svg|webp|gif|ico|woff2?|ttf|json)(?:\\?.*)?$/i.test(url.pathname);\n";
        echo "  if (!isStaticAsset) {\n";
        echo "    return;\n";
        echo "  }\n";
        echo "  event.respondWith(caches.match(event.request).then((cached) => cached || fetch(event.request).then((response) => {\n";
        echo "    if (response && response.ok) {\n";
        echo "      caches.open(CACHE_NAME).then((cache) => cache.put(event.request, response.clone()));\n";
        echo "    }\n";
        echo "    return response;\n";
        echo "  })));\n";
        echo "});\n";
        exit;
    }


    public function handle_calendar_export() {
        $post_id = absint(get_query_var('eep_ics'));
        if (!$post_id || get_post_type($post_id) !== self::POST_TYPE) {
            return;
        }
        $post = get_post($post_id);
        if (!$post || $post->post_status !== 'publish') {
            status_header(404);
            exit;
        }
        $start = strtotime((string) get_post_meta($post_id, 'eep_start', true));
        $end = strtotime((string) get_post_meta($post_id, 'eep_end', true));
        if (!$start) {
            status_header(400);
            exit;
        }
        if (!$end || $end <= $start) {
            $end = $start + HOUR_IN_SECONDS;
        }
        $ics = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//EEP//EN\r\nBEGIN:VEVENT\r\n";
        $ics .= 'UID:eep-' . $post_id . '-' . md5((string) $start) . "@example\r\n";
        $ics .= 'DTSTAMP:' . gmdate('Ymd\THis\Z') . "\r\n";
        $ics .= 'DTSTART:' . gmdate('Ymd\THis\Z', $start) . "\r\n";
        $ics .= 'DTEND:' . gmdate('Ymd\THis\Z', $end) . "\r\n";
        $ics .= 'SUMMARY:' . str_replace(',', '\,', eep_strip_all_tags($post->post_title)) . "\r\n";
        $ics .= 'DESCRIPTION:' . str_replace(["\r", "\n"], [' ', ' '], eep_strip_all_tags($post->post_excerpt ?: $post->post_content)) . "\r\n";
        $ics .= 'LOCATION:' . str_replace(',', '\,', eep_strip_all_tags((string) get_post_meta($post_id, 'eep_address', true))) . "\r\n";
        $ics .= "END:VEVENT\r\nEND:VCALENDAR\r\n";

        nocache_headers();
        header('Content-Type: text/calendar; charset=utf-8');
        header('Content-Disposition: attachment; filename="event-' . $post_id . '.ics"');
        echo $ics;
        exit;
    }


    private function emit_json_ld_schema(array $schema) {
        if (empty($schema)) {
            return;
        }
        $json = wp_json_encode($schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        if (!is_string($json) || $json === '') {
            return;
        }
        echo '<script type="application/ld+json">' . $json . '</script>';
    }


    private function normalize_schema_text($value, $max_length = 500) {
        $text = trim((string) eep_strip_all_tags((string) $value));
        if ($text !== '') {
            $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
        }
        if ($text === '') {
            return '';
        }
        $text = (string) preg_replace('/\s+/u', ' ', $text);
        $max_length = max(40, min(5000, (int) $max_length));
        if (function_exists('mb_strlen') && function_exists('mb_substr')) {
            if (mb_strlen($text, 'UTF-8') > $max_length) {
                $text = rtrim((string) mb_substr($text, 0, $max_length, 'UTF-8')) . '…';
            }
            return $text;
        }
        if (strlen($text) > $max_length) {
            $text = rtrim(substr($text, 0, $max_length)) . '...';
        }
        return $text;
    }


    private function get_schema_home_url() {
        return esc_url_raw((string) home_url('/'));
    }


    private function get_schema_site_logo_url() {
        $logo_url = esc_url_raw((string) get_option('eep_frontend_sticky_nav_logo_url', ''));
        if ($logo_url !== '') {
            return $logo_url;
        }

        $custom_logo_id = absint((int) get_theme_mod('custom_logo'));
        if ($custom_logo_id > 0) {
            $custom_logo_url = esc_url_raw((string) wp_get_attachment_image_url($custom_logo_id, 'full'));
            if ($custom_logo_url !== '') {
                return $custom_logo_url;
            }
        }

        $site_icon_id = absint((int) get_option('site_icon', 0));
        if ($site_icon_id > 0) {
            $site_icon_url = esc_url_raw((string) wp_get_attachment_image_url($site_icon_id, 'full'));
            if ($site_icon_url !== '') {
                return $site_icon_url;
            }
        }

        return '';
    }


    private function build_global_organization_schema() {
        $home_url = $this->get_schema_home_url();
        if ($home_url === '') {
            return [];
        }

        $brand_name = method_exists($this, 'get_white_label_brand_name')
            ? sanitize_text_field((string) $this->get_white_label_brand_name())
            : '';
        if ($brand_name === '') {
            $brand_name = $this->normalize_schema_text((string) get_option('blogname', get_bloginfo('name')), 140);
        }
        if ($brand_name === '') {
            return [];
        }

        $schema = [
            '@context' => 'https://schema.org',
            '@type' => 'Organization',
            '@id' => $home_url . '#organization',
            'name' => $brand_name,
            'url' => $home_url,
        ];

        $description = $this->normalize_schema_text((string) get_bloginfo('description'), 260);
        if ($description !== '') {
            $schema['description'] = $description;
        }

        $logo_url = $this->get_schema_site_logo_url();
        if ($logo_url !== '') {
            $schema['logo'] = [
                '@type' => 'ImageObject',
                'url' => $logo_url,
            ];
        }

        return $schema;
    }


    private function build_global_website_schema() {
        $home_url = $this->get_schema_home_url();
        if ($home_url === '') {
            return [];
        }

        $site_name = method_exists($this, 'get_white_label_brand_name')
            ? sanitize_text_field((string) $this->get_white_label_brand_name())
            : '';
        if ($site_name === '') {
            $site_name = $this->normalize_schema_text((string) get_option('blogname', get_bloginfo('name')), 140);
        }
        if ($site_name === '') {
            return [];
        }

        $schema = [
            '@context' => 'https://schema.org',
            '@type' => 'WebSite',
            '@id' => $home_url . '#website',
            'name' => $site_name,
            'url' => $home_url,
            'inLanguage' => str_replace('_', '-', get_locale()),
            'publisher' => [
                '@id' => $home_url . '#organization',
            ],
        ];

        $description = $this->normalize_schema_text((string) get_bloginfo('description'), 260);
        if ($description !== '') {
            $schema['description'] = $description;
        }

        $search_target = esc_url_raw((string) eep_add_query_arg('s', '{search_term_string}', $home_url));
        if ($search_target !== '') {
            $schema['potentialAction'] = [
                '@type' => 'SearchAction',
                'target' => $search_target,
                'query-input' => 'required name=search_term_string',
            ];
        }

        return $schema;
    }


    private function build_generic_page_schema() {
        $url = '';
        $page_id = absint((int) get_queried_object_id());
        if (is_singular() && $page_id > 0) {
            $permalink = get_permalink($page_id);
            if (is_string($permalink) && $permalink !== '') {
                $url = $permalink;
            }
        }
        if ($url === '') {
            $url = (string) $this->current_url();
        }
        $url = esc_url_raw((string) $url);
        if ($url === '') {
            return [];
        }

        $title = $this->normalize_schema_text((string) wp_get_document_title(), 140);
        if ($title === '' && $page_id > 0) {
            $title = $this->normalize_schema_text(get_the_title($page_id), 140);
        }
        if ($title === '') {
            return [];
        }

        $schema_type = 'WebPage';
        if (is_search()) {
            $schema_type = 'SearchResultsPage';
        } elseif (is_author()) {
            $schema_type = 'ProfilePage';
        } elseif (is_home() || is_front_page() || is_archive()) {
            $schema_type = 'CollectionPage';
        }

        $home_url = $this->get_schema_home_url();
        $schema = [
            '@context' => 'https://schema.org',
            '@type' => $schema_type,
            '@id' => $url . '#webpage',
            'name' => $title,
            'url' => $url,
            'inLanguage' => str_replace('_', '-', get_locale()),
        ];
        if ($home_url !== '') {
            $schema['isPartOf'] = [
                '@id' => $home_url . '#website',
            ];
        }

        $description = '';
        if ($page_id > 0) {
            $excerpt = (string) get_post_field('post_excerpt', $page_id);
            $content = (string) get_post_field('post_content', $page_id);
            if ($excerpt !== '') {
                $description = $this->normalize_schema_text($excerpt, 360);
            } elseif ($content !== '') {
                $description = $this->normalize_schema_text($content, 360);
            }
        } elseif (is_tax() || is_category() || is_tag()) {
            $term = get_queried_object();
            if ($term instanceof WP_Term) {
                $description = $this->normalize_schema_text((string) term_description((int) $term->term_id, (string) $term->taxonomy), 360);
            }
        } elseif (is_author()) {
            $author_object = get_queried_object();
            if ($author_object instanceof WP_User) {
                $description = $this->normalize_schema_text((string) $author_object->description, 360);
            } elseif (is_object($author_object) && isset($author_object->description)) {
                $description = $this->normalize_schema_text((string) $author_object->description, 360);
            }
        } elseif (is_search()) {
            $search_query = $this->normalize_schema_text(get_search_query(false), 100);
            if ($search_query !== '') {
                $description = $this->normalize_schema_text(
                    sprintf(
                        /* translators: %s: search query */
                        __('Search results for "%s".', 'nexdirectory'),
                        $search_query
                    ),
                    360
                );
            }
        }

        if ($description === '') {
            $description = $this->normalize_schema_text((string) get_bloginfo('description'), 360);
        }
        if ($description !== '') {
            $schema['description'] = $description;
        }

        if ($page_id > 0) {
            $featured_image_url = esc_url_raw((string) get_the_post_thumbnail_url($page_id, 'full'));
            if ($featured_image_url !== '') {
                $schema['primaryImageOfPage'] = [
                    '@type' => 'ImageObject',
                    'url' => $featured_image_url,
                ];
            }
        }

        if (is_search()) {
            $search_target = esc_url_raw((string) eep_add_query_arg('s', '{search_term_string}', $home_url !== '' ? $home_url : $url));
            if ($search_target !== '') {
                $schema['potentialAction'] = [
                    '@type' => 'SearchAction',
                    'target' => $search_target,
                    'query-input' => 'required name=search_term_string',
                ];
            }
        }

        if (is_author()) {
            $author_object = get_queried_object();
            if ($author_object instanceof WP_User) {
                $person_schema = $this->build_person_schema_from_user($author_object, $url);
            } elseif (is_object($author_object) && isset($author_object->ID)) {
                $person_schema = $this->build_person_schema_from_user((int) $author_object->ID, $url);
            } else {
                $person_schema = [];
            }
            if (!empty($person_schema['@id'])) {
                $schema['mainEntity'] = [
                    '@id' => (string) $person_schema['@id'],
                ];
            }
        }

        return $schema;
    }


    private function build_generic_breadcrumb_schema($current_label = '') {
        if (is_front_page()) {
            return [];
        }

        $home_url = $this->get_schema_home_url();
        if ($home_url === '') {
            $home_url = esc_url_raw((string) $this->current_url());
        }

        $items = [];
        $items[] = [
            'label' => __('Home', 'nexdirectory'),
            'url' => $home_url,
        ];

        $current_label = $this->normalize_schema_text($current_label, 140);
        if (is_singular()) {
            $page_id = absint((int) get_queried_object_id());
            if ($page_id > 0) {
                $ancestors = array_reverse((array) get_post_ancestors($page_id));
                foreach ($ancestors as $ancestor_id) {
                    $ancestor_id = absint((int) $ancestor_id);
                    if ($ancestor_id <= 0) {
                        continue;
                    }
                    $ancestor_label = $this->normalize_schema_text(get_the_title($ancestor_id), 120);
                    $ancestor_url = esc_url_raw((string) get_permalink($ancestor_id));
                    if ($ancestor_label === '' || $ancestor_url === '') {
                        continue;
                    }
                    $items[] = [
                        'label' => $ancestor_label,
                        'url' => $ancestor_url,
                    ];
                }

                $page_label = $current_label !== '' ? $current_label : $this->normalize_schema_text(get_the_title($page_id), 140);
                $page_url = esc_url_raw((string) get_permalink($page_id));
                if ($page_label !== '' && $page_url !== '') {
                    $items[] = [
                        'label' => $page_label,
                        'url' => $page_url,
                    ];
                }
            }
        } elseif (is_tax() || is_category() || is_tag()) {
            $term = get_queried_object();
            if ($term instanceof WP_Term) {
                $parent_ids = array_reverse((array) get_ancestors((int) $term->term_id, (string) $term->taxonomy, 'taxonomy'));
                foreach ($parent_ids as $parent_id) {
                    $parent_id = absint((int) $parent_id);
                    if ($parent_id <= 0) {
                        continue;
                    }
                    $parent_term = get_term($parent_id, (string) $term->taxonomy);
                    if (!$parent_term instanceof WP_Term) {
                        continue;
                    }
                    $parent_label = $this->normalize_schema_text((string) $parent_term->name, 120);
                    $parent_link = get_term_link($parent_term);
                    $parent_url = is_wp_error($parent_link) ? '' : esc_url_raw((string) $parent_link);
                    if ($parent_label === '' || $parent_url === '') {
                        continue;
                    }
                    $items[] = [
                        'label' => $parent_label,
                        'url' => $parent_url,
                    ];
                }

                $term_label = $current_label !== '' ? $current_label : $this->normalize_schema_text((string) $term->name, 140);
                $term_link = get_term_link($term);
                $term_url = is_wp_error($term_link) ? '' : esc_url_raw((string) $term_link);
                if ($term_label !== '' && $term_url !== '') {
                    $items[] = [
                        'label' => $term_label,
                        'url' => $term_url,
                    ];
                }
            }
        } elseif (is_author()) {
            $author_object = get_queried_object();
            if ($author_object instanceof WP_User) {
                $author_label = $current_label !== '' ? $current_label : $this->normalize_schema_text((string) $author_object->display_name, 140);
                $author_url = esc_url_raw((string) get_author_posts_url((int) $author_object->ID));
                if ($author_label !== '' && $author_url !== '') {
                    $items[] = [
                        'label' => $author_label,
                        'url' => $author_url,
                    ];
                }
            } elseif (is_object($author_object) && isset($author_object->ID)) {
                $author_id = absint((int) $author_object->ID);
                $author_label = $current_label !== '' ? $current_label : $this->normalize_schema_text((string) ($author_object->display_name ?? ''), 140);
                $author_url = esc_url_raw((string) get_author_posts_url($author_id));
                if ($author_label !== '' && $author_url !== '') {
                    $items[] = [
                        'label' => $author_label,
                        'url' => $author_url,
                    ];
                }
            }
        } else {
            $archive_title = $current_label !== '' ? $current_label : $this->normalize_schema_text((string) wp_get_document_title(), 140);
            $archive_url = esc_url_raw((string) $this->current_url());
            if ($archive_title !== '' && $archive_url !== '') {
                $items[] = [
                    'label' => $archive_title,
                    'url' => $archive_url,
                ];
            }
        }

        if (count($items) < 2) {
            return [];
        }

        $crumbs = [];
        $position = 1;
        foreach ($items as $item) {
            $label = $this->normalize_schema_text((string) ($item['label'] ?? ''), 140);
            $item_url = esc_url_raw((string) ($item['url'] ?? ''));
            if ($label === '' || $item_url === '') {
                continue;
            }
            $crumbs[] = [
                '@type' => 'ListItem',
                'position' => $position,
                'item' => [
                    '@id' => $item_url,
                    'name' => $label,
                ],
            ];
            $position++;
        }

        if (count($crumbs) < 2) {
            return [];
        }

        return [
            '@context' => 'https://schema.org',
            '@type' => 'BreadcrumbList',
            'itemListElement' => $crumbs,
        ];
    }


    private function build_person_schema_from_user($user, $primary_url = '') {
        $user_obj = null;
        if ($user instanceof WP_User) {
            $user_obj = $user;
        } else {
            $user_id = absint((int) $user);
            if ($user_id > 0) {
                $resolved = get_user_by('id', $user_id);
                if ($resolved instanceof WP_User) {
                    $user_obj = $resolved;
                }
            }
        }
        if (!$user_obj instanceof WP_User) {
            return [];
        }

        $name = $this->normalize_schema_text((string) $user_obj->display_name, 140);
        if ($name === '') {
            $name = $this->normalize_schema_text((string) $user_obj->user_login, 140);
        }
        if ($name === '') {
            return [];
        }

        $profile_url = esc_url_raw((string) get_author_posts_url((int) $user_obj->ID));
        if ($profile_url === '') {
            $profile_url = esc_url_raw((string) $primary_url);
        }

        $person_id = $profile_url !== ''
            ? $profile_url . '#person'
            : $this->get_schema_home_url() . '#person-' . (int) $user_obj->ID;

        $schema = [
            '@context' => 'https://schema.org',
            '@type' => 'Person',
            '@id' => $person_id,
            'name' => $name,
        ];
        if ($profile_url !== '') {
            $schema['url'] = $profile_url;
        }

        $bio = $this->normalize_schema_text((string) get_user_meta((int) $user_obj->ID, 'description', true), 600);
        if ($bio !== '') {
            $schema['description'] = $bio;
        }

        $avatar_url = esc_url_raw((string) get_avatar_url((int) $user_obj->ID, ['size' => 512]));
        if ($avatar_url !== '') {
            $schema['image'] = $avatar_url;
        }

        return $schema;
    }


    private function build_event_speaker_schema_payloads($post_id, $event_url = '') {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return [
                'schemas' => [],
                'references' => [],
            ];
        }

        $speakers = $this->parse_key_speakers((string) get_post_meta($post_id, 'eep_key_speakers', true));
        if (!is_array($speakers) || empty($speakers)) {
            return [
                'schemas' => [],
                'references' => [],
            ];
        }

        $event_url = esc_url_raw((string) $event_url);
        $speaker_counts = [];
        $speaker_schemas = [];
        $speaker_refs = [];
        foreach (array_values(array_slice((array) $speakers, 0, 24)) as $index => $speaker_row) {
            if (!is_array($speaker_row)) {
                continue;
            }
            $name = $this->normalize_schema_text((string) ($speaker_row['name'] ?? ''), 140);
            if ($name === '') {
                continue;
            }
            $slug_base = sanitize_title($name);
            if ($slug_base === '') {
                $slug_base = 'speaker-' . ((int) $index + 1);
            }
            $slug_count = isset($speaker_counts[$slug_base]) ? ((int) $speaker_counts[$slug_base] + 1) : 1;
            $speaker_counts[$slug_base] = $slug_count;
            $slug = $slug_base . ($slug_count > 1 ? '-' . $slug_count : '');
            $speaker_id = $event_url !== ''
                ? $event_url . '#speaker-' . $slug
                : $this->get_schema_home_url() . '#speaker-' . (int) $post_id . '-' . ((int) $index + 1);

            $speaker_schema = [
                '@context' => 'https://schema.org',
                '@type' => 'Person',
                '@id' => $speaker_id,
                'name' => $name,
            ];

            $profession = $this->normalize_schema_text((string) ($speaker_row['profession'] ?? ''), 140);
            if ($profession !== '') {
                $speaker_schema['jobTitle'] = $profession;
            }
            $subject = $this->normalize_schema_text((string) ($speaker_row['subject'] ?? ''), 180);
            if ($subject !== '') {
                $speaker_schema['knowsAbout'] = $subject;
            }
            $details = $this->normalize_schema_text((string) ($speaker_row['details'] ?? ''), 500);
            if ($details !== '') {
                $speaker_schema['description'] = $details;
            }
            $image_url = esc_url_raw((string) ($speaker_row['image'] ?? ''));
            if ($image_url !== '') {
                $speaker_schema['image'] = $image_url;
            }

            $speaker_schemas[] = $speaker_schema;
            $speaker_refs[] = [
                '@id' => $speaker_id,
            ];
        }

        return [
            'schemas' => $speaker_schemas,
            'references' => $speaker_refs,
        ];
    }


    private function build_event_sub_event_schema_items($post_id, $event_tz, $fallback_start_dt, $fallback_end_dt) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return [];
        }

        $sessions = $this->parse_sessions((string) get_post_meta($post_id, 'eep_sessions', true));
        if (!is_array($sessions) || empty($sessions)) {
            return [];
        }

        if (!$event_tz instanceof DateTimeZone) {
            try {
                $event_tz = new DateTimeZone('UTC');
            } catch (Exception $e) {
                return [];
            }
        }

        $fallback_start_ts = $fallback_start_dt instanceof DateTime ? (int) $fallback_start_dt->getTimestamp() : 0;
        $fallback_end_ts = $fallback_end_dt instanceof DateTime ? (int) $fallback_end_dt->getTimestamp() : 0;
        if ($fallback_end_ts > 0 && $fallback_start_ts > 0 && $fallback_end_ts <= $fallback_start_ts) {
            $fallback_end_ts = $fallback_start_ts + HOUR_IN_SECONDS;
        }

        $sub_events = [];
        foreach (array_values(array_slice((array) $sessions, 0, 30)) as $index => $session_row) {
            if (!is_array($session_row)) {
                continue;
            }

            $session_title = $this->normalize_schema_text((string) ($session_row['title'] ?? ''), 180);
            if ($session_title === '') {
                $session_title = sprintf(__('Session %d', 'nexdirectory'), (int) $index + 1);
            }

            $session_start_raw = trim((string) ($session_row['start'] ?? ''));
            $session_end_raw = trim((string) ($session_row['end'] ?? ''));
            $session_start_dt = null;
            $session_end_dt = null;

            try {
                if ($session_start_raw !== '') {
                    $session_start_dt = new DateTime($session_start_raw, $event_tz);
                }
            } catch (Exception $e) {
                $session_start_dt = null;
            }
            if (!$session_start_dt instanceof DateTime && $fallback_start_ts > 0) {
                try {
                    $session_start_dt = new DateTime('@' . $fallback_start_ts);
                    $session_start_dt->setTimezone($event_tz);
                } catch (Exception $e) {
                    $session_start_dt = null;
                }
            }

            try {
                if ($session_end_raw !== '') {
                    $session_end_dt = new DateTime($session_end_raw, $event_tz);
                }
            } catch (Exception $e) {
                $session_end_dt = null;
            }
            if (!$session_end_dt instanceof DateTime && $session_start_dt instanceof DateTime) {
                $session_end_dt = clone $session_start_dt;
                $session_end_dt->modify('+1 hour');
            }
            if (!$session_end_dt instanceof DateTime && $fallback_end_ts > 0) {
                try {
                    $session_end_dt = new DateTime('@' . $fallback_end_ts);
                    $session_end_dt->setTimezone($event_tz);
                } catch (Exception $e) {
                    $session_end_dt = null;
                }
            }
            if ($session_start_dt instanceof DateTime && $session_end_dt instanceof DateTime && $session_end_dt <= $session_start_dt) {
                $session_end_dt = clone $session_start_dt;
                $session_end_dt->modify('+1 hour');
            }

            $session_location = $this->normalize_schema_text((string) ($session_row['location'] ?? ''), 180);
            $session_speaker = $this->normalize_schema_text((string) ($session_row['speaker'] ?? ''), 140);
            $sub_event = [
                '@type' => 'Event',
                'name' => $session_title,
            ];
            if ($session_start_dt instanceof DateTime) {
                $sub_event['startDate'] = $session_start_dt->format('c');
            }
            if ($session_end_dt instanceof DateTime) {
                $sub_event['endDate'] = $session_end_dt->format('c');
            }
            if ($session_location !== '') {
                $sub_event['location'] = [
                    '@type' => 'Place',
                    'name' => $session_location,
                ];
            }
            if ($session_speaker !== '') {
                $sub_event['performer'] = [
                    '@type' => 'Person',
                    'name' => $session_speaker,
                ];
            }

            $sub_events[] = $sub_event;
        }

        return $sub_events;
    }


    private function build_supporting_document_schema_items($post_id, $limit = 6) {
        $post_id = absint((int) $post_id);
        $limit = max(1, min(20, (int) $limit));
        if ($post_id <= 0) {
            return [];
        }
        $urls = $this->parse_multiline_urls((string) get_post_meta($post_id, 'eep_supporting_docs', true));
        if (empty($urls)) {
            return [];
        }
        $items = [];
        foreach (array_slice((array) $urls, 0, $limit) as $index => $url) {
            $url = esc_url_raw((string) $url);
            if ($url === '') {
                continue;
            }
            $path = (string) wp_parse_url($url, PHP_URL_PATH);
            $extension = strtolower((string) pathinfo($path, PATHINFO_EXTENSION));
            $mime = wp_check_filetype($url);
            $media_item = [
                '@type' => 'MediaObject',
                'contentUrl' => $url,
                'url' => $url,
                'name' => sprintf(__('Document %d', 'nexdirectory'), (int) $index + 1),
            ];
            if (!empty($mime['type'])) {
                $media_item['encodingFormat'] = sanitize_text_field((string) $mime['type']);
            } elseif ($extension !== '') {
                $media_item['encodingFormat'] = 'application/' . sanitize_key($extension);
            }
            $items[] = $media_item;
        }
        return $items;
    }


    private function get_platform_schema_shortcode_map() {
        $prefixes = ['eep', 'nexdirectory'];
        $suffixes_by_context = [
            'archive' => ['events_archive', 'listings_archive', 'events_query_grid', 'events_bento_grid', 'events_ajax_search', 'quick_search'],
            'map' => ['events_map', 'listings_map'],
            'calendar' => ['events_calendar', 'listings_calendar'],
            'submit' => ['submit_event', 'submit'],
            'my_items' => ['my_events', 'my_listings'],
            'boost' => ['boost_posts'],
            'attending' => ['my_tickets', 'saved_listings'],
            'attendees' => ['whos_attending', 'attendees'],
            'analytics' => ['owner_analytics'],
            'notifications' => ['notifications'],
            'whats_new' => ['whats_new'],
            'account' => ['account'],
            'auth' => ['auth'],
            'user_management' => ['user_management'],
            'moderation' => ['moderation_queue'],
            'organizers' => ['organizers'],
            'claim' => ['claim_listing'],
            'chatbot' => ['event_chatbot'],
        ];

        $map = [];
        foreach ($suffixes_by_context as $context => $suffixes) {
            $tags = [];
            foreach ($prefixes as $prefix) {
                foreach ((array) $suffixes as $suffix) {
                    $tag = sanitize_key((string) $prefix . '_' . $suffix);
                    if ($tag !== '') {
                        $tags[] = $tag;
                    }
                }
            }
            $map[$context] = array_values(array_unique($tags));
        }
        return $map;
    }


    private function detect_platform_schema_page_context() {
        if (is_post_type_archive(self::POST_TYPE)) {
            return 'archive';
        }

        if (is_tax()) {
            $queried_tax = sanitize_key((string) get_query_var('taxonomy'));
            $supported_taxonomies = [
                self::TAX_TYPE,
                self::TAX_MODE,
                self::TAX_TAG,
                self::TAX_PRICE,
            ];
            $supported_taxonomies = array_merge($supported_taxonomies, array_keys((array) get_option('eep_custom_taxonomies', [])));
            if (in_array($queried_tax, array_map('sanitize_key', $supported_taxonomies), true)) {
                return 'archive';
            }
        }

        if (is_search()) {
            $post_type = get_query_var('post_type');
            if ($post_type === self::POST_TYPE || (is_array($post_type) && in_array(self::POST_TYPE, $post_type, true))) {
                return 'archive';
            }
        }

        if (!is_singular() || is_singular(self::POST_TYPE)) {
            return '';
        }

        $page = get_queried_object();
        if (!$page instanceof WP_Post) {
            return '';
        }

        $content = (string) $page->post_content;
        if ($content !== '') {
            $shortcode_map = $this->get_platform_schema_shortcode_map();
            foreach ($shortcode_map as $context => $tags) {
                foreach ((array) $tags as $tag) {
                    if (has_shortcode($content, (string) $tag)) {
                        return (string) $context;
                    }
                }
            }
        }

        $page_id = absint((int) $page->ID);
        if ($page_id > 0) {
            $profile_key = $this->get_site_profile_key();
            $route_context_map = [
                'archive' => ['option' => 'eep_page_events_url', 'route' => 'events'],
                'map' => ['option' => 'eep_page_events_map_url', 'route' => 'map'],
                'calendar' => ['option' => 'eep_page_events_calendar_url', 'route' => 'calendar'],
                'submit' => ['option' => 'eep_page_submit_event_url', 'route' => 'submit'],
                'my_items' => ['option' => 'eep_page_my_events_url', 'route' => 'my_items'],
                'boost' => ['option' => 'eep_page_boost_posts_url', 'route' => 'boost'],
                'analytics' => ['option' => 'eep_page_owner_analytics_url', 'route' => 'analytics'],
                'attending' => ['option' => 'eep_page_my_tickets_url', 'route' => 'attending'],
                'attendees' => ['option' => 'eep_page_whos_attending_url', 'route' => 'attendees'],
                'account' => ['option' => 'eep_page_account_url', 'route' => 'account'],
                'auth' => ['option' => 'eep_page_login_signup_url', 'route' => 'auth'],
                'claim' => ['option' => 'eep_claim_listing_page_url', 'route' => 'claim'],
            ];
            foreach ($route_context_map as $context => $route_definition) {
                $route_option = (string) ($route_definition['option'] ?? '');
                $route_key = sanitize_key((string) ($route_definition['route'] ?? ''));
                if ($route_option === '' || $route_key === '') {
                    continue;
                }
                $route_url = $this->get_platform_page_url(
                    $route_option,
                    $this->get_profile_route_slugs($route_key, $profile_key),
                    $this->get_profile_route_default_url($route_key, $profile_key)
                );
                $route_page_id = absint((int) url_to_postid((string) $route_url));
                if ($route_page_id > 0 && $route_page_id === $page_id) {
                    return (string) $context;
                }
            }
        }

        $slug = eep_sanitize_title((string) $page->post_name);
        $slug_map = [
            'whats-new' => 'whats_new',
            'notifications' => 'notifications',
            'user-management' => 'user_management',
            'moderation-queue' => 'moderation',
            'organizers' => 'organizers',
            'boost-post' => 'boost',
            'boost-posts' => 'boost',
        ];
        return isset($slug_map[$slug]) ? (string) $slug_map[$slug] : '';
    }


    private function build_platform_page_item_list_schema($context, array $ui_labels, $limit = 10) {
        $context = sanitize_key((string) $context);
        $limit = max(1, min(30, (int) $limit));
        if ($context === '') {
            return [];
        }

        $query = null;
        if ($context === 'whats_new') {
            $query = new WP_Query([
                'post_type' => self::POST_TYPE,
                'post_status' => 'publish',
                'posts_per_page' => $limit,
                'orderby' => 'date',
                'order' => 'DESC',
                'ignore_sticky_posts' => true,
                'no_found_rows' => true,
            ]);
        } else {
            $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' => isset($_GET['s']) ? sanitize_text_field((string) wp_unslash($_GET['s'])) : '',
                'event_type' => isset($_GET['event_type']) ? sanitize_text_field((string) wp_unslash($_GET['event_type'])) : '',
                'event_mode' => isset($_GET['event_mode']) ? sanitize_text_field((string) wp_unslash($_GET['event_mode'])) : '',
                'event_price' => isset($_GET['event_price']) ? sanitize_text_field((string) wp_unslash($_GET['event_price'])) : '',
                'event_tag' => isset($_GET['event_tag']) ? sanitize_text_field((string) wp_unslash($_GET['event_tag'])) : '',
                'city' => isset($_GET['city']) ? sanitize_text_field((string) wp_unslash($_GET['city'])) : '',
                'date_from' => isset($_GET['date_from']) ? sanitize_text_field((string) wp_unslash($_GET['date_from'])) : '',
                'official' => isset($_GET['official']) ? sanitize_text_field((string) wp_unslash($_GET['official'])) : '',
            ];
            if ($is_job_profile) {
                $filters['event_price'] = '';
            }
            if (!$is_events_profile) {
                $filters['official'] = '';
            }
            $organizer_id = isset($_GET['organizer']) ? absint((int) $_GET['organizer']) : (isset($_GET['author']) ? absint((int) $_GET['author']) : 0);
            $query = $this->build_archive_query($filters, $limit, 1, $organizer_id);
        }

        if (!$query instanceof WP_Query || empty($query->posts)) {
            return [];
        }

        $elements = [];
        $position = 1;
        foreach ((array) $query->posts as $post_item) {
            $post_id = is_object($post_item) ? absint((int) ($post_item->ID ?? 0)) : absint((int) $post_item);
            if ($post_id <= 0 || get_post_status($post_id) !== 'publish') {
                continue;
            }
            $item_url = get_permalink($post_id);
            if (!is_string($item_url) || $item_url === '') {
                continue;
            }
            $item = [
                '@type' => 'Thing',
                '@id' => esc_url_raw($item_url),
                'name' => $this->normalize_schema_text(get_the_title($post_id), 140),
            ];
            $item_profile_key = $this->resolve_profile_key_for_post($post_id);
            if ($item_profile_key === 'events') {
                $item['@type'] = 'Event';
                $start_raw = (string) get_post_meta($post_id, 'eep_start', true);
                $start_ts = $start_raw !== '' ? strtotime($start_raw) : false;
                if ($start_ts) {
                    $item['startDate'] = gmdate('c', (int) $start_ts);
                }
            } elseif ($item_profile_key === 'job_board') {
                $item['@type'] = 'JobPosting';
                $posted_raw = (string) get_post_field('post_date', $post_id);
                $posted_ts = $posted_raw !== '' ? strtotime($posted_raw) : false;
                if ($posted_ts) {
                    $item['datePosted'] = gmdate('c', (int) $posted_ts);
                }
            } elseif ($item_profile_key === 'directory') {
                $item['@type'] = 'LocalBusiness';
            } elseif ($item_profile_key === 'marketplace') {
                $item['@type'] = 'Product';
            }

            $elements[] = [
                '@type' => 'ListItem',
                'position' => $position,
                'item' => $item,
            ];
            $position++;
        }
        wp_reset_postdata();

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

        return [
            '@type' => 'ItemList',
            'itemListOrder' => 'https://schema.org/ItemListOrderAscending',
            'numberOfItems' => count($elements),
            'itemListElement' => $elements,
        ];
    }


    private function build_platform_page_schema($context, array $ui_labels = []) {
        $context = sanitize_key((string) $context);
        if ($context === '') {
            return [];
        }
        $page_id = absint((int) get_queried_object_id());
        $url = $page_id > 0 ? get_permalink($page_id) : $this->current_url();
        if (!is_string($url) || $url === '') {
            $url = $this->current_url();
        }
        $url = esc_url_raw((string) $url);
        if ($url === '') {
            return [];
        }

        $title = $page_id > 0 ? $this->normalize_schema_text(get_the_title($page_id), 140) : '';
        if ($title === '') {
            $title = $this->normalize_schema_text((string) wp_get_document_title(), 140);
        }
        if ($title === '') {
            return [];
        }

        $description = '';
        if ($page_id > 0) {
            $excerpt = (string) get_post_field('post_excerpt', $page_id);
            $content = (string) get_post_field('post_content', $page_id);
            if ($excerpt !== '') {
                $description = $this->normalize_schema_text($excerpt, 340);
            } elseif ($content !== '') {
                $description = $this->normalize_schema_text($content, 340);
            }
        }
        if ($description === '') {
            $description_map = [
                'archive' => (string) ($ui_labels['archive_description'] ?? ''),
                'map' => (string) ($ui_labels['map_description'] ?? ''),
                'calendar' => (string) ($ui_labels['calendar_description'] ?? ''),
                'submit' => (string) ($ui_labels['submit_add_description'] ?? ''),
                'my_items' => (string) ($ui_labels['my_items_description'] ?? ''),
                'boost' => __('Promote your listings with paid boosts, renewals, and boost history.', 'nexdirectory'),
                'attending' => (string) ($ui_labels['attending_description'] ?? ''),
                'analytics' => (string) ($ui_labels['owner_analytics_description'] ?? ''),
                'account' => (string) ($ui_labels['account_description'] ?? ''),
                'organizers' => (string) ($ui_labels['organizers_description'] ?? ''),
                'whats_new' => (string) ($ui_labels['whats_new_description'] ?? ''),
                'auth' => __('Login or create an account to access listing actions.', 'nexdirectory'),
                'claim' => __('Claim this listing if you are the legitimate owner.', 'nexdirectory'),
                'moderation' => __('Review pending listings and apply moderation actions.', 'nexdirectory'),
                'user_management' => __('Manage user access and listing permissions.', 'nexdirectory'),
                'notifications' => __('Review your platform notifications and updates.', 'nexdirectory'),
                'attendees' => __('Review users who saved or RSVPed this listing.', 'nexdirectory'),
                'chatbot' => __('Ask the assistant to find relevant listings quickly.', 'nexdirectory'),
            ];
            $description = $this->normalize_schema_text((string) ($description_map[$context] ?? ''), 340);
        }

        $collection_contexts = ['archive', 'map', 'calendar', 'whats_new'];
        $schema_type = in_array($context, $collection_contexts, true) ? 'CollectionPage' : 'WebPage';
        if ($context === 'account') {
            $schema_type = 'ProfilePage';
        }

        $schema = [
            '@context' => 'https://schema.org',
            '@type' => $schema_type,
            'name' => $title,
            'url' => $url,
            'inLanguage' => str_replace('_', '-', get_locale()),
            'isPartOf' => [
                '@type' => 'WebSite',
                'name' => sanitize_text_field((string) $this->get_white_label_brand_name()),
                'url' => esc_url_raw(home_url('/')),
            ],
        ];
        if ($description !== '') {
            $schema['description'] = $description;
        }

        if (in_array($context, $collection_contexts, true)) {
            $item_list = $this->build_platform_page_item_list_schema($context, $ui_labels, 12);
            if (!empty($item_list)) {
                $schema['mainEntity'] = $item_list;
            }
        }

        if (in_array($context, ['archive', 'map', 'calendar'], true)) {
            $schema['potentialAction'] = [
                '@type' => 'SearchAction',
                'target' => esc_url_raw((string) eep_add_query_arg('s', '{search_term_string}', $url)),
                'query-input' => 'required name=search_term_string',
            ];
        } elseif ($context === 'submit') {
            $schema['potentialAction'] = [
                '@type' => 'CreateAction',
                'name' => sprintf(
                    /* translators: %s: singular label */
                    __('Submit %s', 'nexdirectory'),
                    sanitize_text_field((string) ($ui_labels['singular'] ?? __('Listing', 'nexdirectory')))
                ),
                'target' => $url,
            ];
        } elseif ($context === 'auth') {
            $schema['potentialAction'] = [
                '@type' => 'LoginAction',
                'target' => $url,
            ];
        }

        return $schema;
    }


    private function build_platform_breadcrumb_schema($current_label = '') {
        $is_listing = is_singular(self::POST_TYPE);
        $page_context = $is_listing ? '' : $this->detect_platform_schema_page_context();
        $is_page_context = !$is_listing && $page_context !== '';
        if (!$is_listing && !$is_page_context) {
            return [];
        }

        $items = [];
        $items[] = [
            'label' => __('Home', 'nexdirectory'),
            'url' => esc_url_raw(home_url('/')),
        ];

        $current_label = $this->normalize_schema_text($current_label, 140);
        if ($is_listing) {
            $post_id = absint((int) get_queried_object_id());
            if ($post_id <= 0) {
                return [];
            }
            $profile_key = $this->resolve_profile_key_for_post($post_id);
            $ui_labels = $this->get_site_profile_ui_labels($profile_key);
            $plural_label = sanitize_text_field((string) ($ui_labels['plural'] ?? __('Listings', 'nexdirectory')));
            $items[] = [
                'label' => $this->normalize_schema_text($plural_label, 100),
                'url' => $this->get_platform_page_url(
                    'eep_page_events_url',
                    $this->get_profile_route_slugs('events', $profile_key),
                    $this->get_profile_route_default_url('events', $profile_key)
                ),
            ];
            $listing_title = $current_label !== '' ? $current_label : $this->normalize_schema_text(get_the_title($post_id), 140);
            if ($listing_title !== '') {
                $items[] = [
                    'label' => $listing_title,
                    'url' => get_permalink($post_id),
                ];
            }
        } else {
            $page_id = absint((int) get_queried_object_id());
            if ($page_id > 0 && is_singular()) {
                $ancestors = array_reverse((array) get_post_ancestors($page_id));
                foreach ($ancestors as $ancestor_id) {
                    $ancestor_id = absint((int) $ancestor_id);
                    if ($ancestor_id <= 0) {
                        continue;
                    }
                    $ancestor_label = $this->normalize_schema_text(get_the_title($ancestor_id), 120);
                    $ancestor_url = get_permalink($ancestor_id);
                    if ($ancestor_label === '' || !is_string($ancestor_url) || $ancestor_url === '') {
                        continue;
                    }
                    $items[] = [
                        'label' => $ancestor_label,
                        'url' => esc_url_raw((string) $ancestor_url),
                    ];
                }
                $page_label = $current_label !== '' ? $current_label : $this->normalize_schema_text(get_the_title($page_id), 140);
                if ($page_label !== '') {
                    $items[] = [
                        'label' => $page_label,
                        'url' => get_permalink($page_id),
                    ];
                }
            } else {
                $archive_title = $current_label !== '' ? $current_label : $this->normalize_schema_text((string) wp_get_document_title(), 140);
                if ($archive_title !== '') {
                    $items[] = [
                        'label' => $archive_title,
                        'url' => esc_url_raw((string) $this->current_url()),
                    ];
                }
            }
        }

        if (count($items) < 2) {
            return [];
        }

        $crumbs = [];
        $position = 1;
        foreach ($items as $item) {
            $label = $this->normalize_schema_text((string) ($item['label'] ?? ''), 140);
            $item_url = esc_url_raw((string) ($item['url'] ?? ''));
            if ($label === '') {
                continue;
            }
            if ($item_url === '') {
                $item_url = esc_url_raw((string) $this->current_url());
            }
            $crumbs[] = [
                '@type' => 'ListItem',
                'position' => $position,
                'item' => [
                    '@id' => $item_url,
                    'name' => $label,
                ],
            ];
            $position++;
        }

        if (count($crumbs) < 2) {
            return [];
        }

        return [
            '@context' => 'https://schema.org',
            '@type' => 'BreadcrumbList',
            'itemListElement' => $crumbs,
        ];
    }


    private function build_directory_listing_schema_payload($post_id, array $ui_labels = []) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return [];
        }

        $title = $this->normalize_schema_text(get_the_title($post_id), 180);
        $description = $this->normalize_schema_text((string) (get_post_field('post_excerpt', $post_id) ?: get_post_field('post_content', $post_id)), 2400);
        $permalink = esc_url_raw((string) get_permalink($post_id));
        if ($title === '' || $permalink === '') {
            return [];
        }

        $business_type = sanitize_key((string) get_post_meta($post_id, 'eep_directory_business_type', true));
        $schema_type = 'LocalBusiness';
        if (in_array($business_type, ['professional-service', 'professional_service', 'service', 'consultancy', 'agency'], true)) {
            $schema_type = 'ProfessionalService';
        } elseif (in_array($business_type, ['restaurant', 'hospitality', 'food'], true)) {
            $schema_type = 'Restaurant';
        } elseif (in_array($business_type, ['health', 'clinic', 'wellness'], true)) {
            $schema_type = 'MedicalBusiness';
        }

        $schema = [
            '@context' => 'https://schema.org',
            '@type' => $schema_type,
            'name' => $title,
            'url' => $permalink,
            'mainEntityOfPage' => $permalink,
        ];
        if ($description !== '') {
            $schema['description'] = $description;
        }

        $images = [];
        $cover = $this->get_event_cover_image_url($post_id, 'full', 'hero');
        if ($cover !== '') {
            $images[] = esc_url_raw((string) $cover);
        }
        foreach ((array) $this->get_event_gallery_image_urls($post_id, 'full') as $gallery_url) {
            $gallery_url = esc_url_raw((string) $gallery_url);
            if ($gallery_url !== '') {
                $images[] = $gallery_url;
            }
        }
        $images = array_values(array_unique(array_filter($images)));
        if (!empty($images)) {
            $schema['image'] = $images;
        }

        $phone = sanitize_text_field((string) get_post_meta($post_id, 'eep_contact_phone', true));
        $email = sanitize_email((string) get_post_meta($post_id, 'eep_contact_email', true));
        if ($phone !== '') {
            $schema['telephone'] = $phone;
        }
        if (is_email($email)) {
            $schema['email'] = $email;
        }

        $website = esc_url_raw((string) get_post_meta($post_id, 'eep_external_url', true));
        if ($website === '') {
            $website = esc_url_raw((string) get_post_meta($post_id, 'eep_directory_appointment_url', true));
        }
        if ($website !== '') {
            $schema['sameAs'] = [$website];
        }

        $address = trim((string) get_post_meta($post_id, 'eep_address', true));
        $city = trim((string) get_post_meta($post_id, 'eep_city', true));
        $country = trim((string) get_post_meta($post_id, 'eep_country', true));
        if ($address !== '' || $city !== '' || $country !== '') {
            $schema['address'] = [
                '@type' => 'PostalAddress',
                'streetAddress' => $this->normalize_schema_text($address, 180),
                'addressLocality' => $this->normalize_schema_text($city, 100),
                'addressCountry' => $this->normalize_schema_text($country, 100),
            ];
        }
        $lat = get_post_meta($post_id, 'eep_lat', true);
        $lng = get_post_meta($post_id, 'eep_lng', true);
        if (is_numeric($lat) && is_numeric($lng)) {
            $schema['geo'] = [
                '@type' => 'GeoCoordinates',
                'latitude' => (float) $lat,
                'longitude' => (float) $lng,
            ];
        }

        $service_area = $this->normalize_schema_text((string) get_post_meta($post_id, 'eep_directory_service_area', true), 180);
        if ($service_area !== '') {
            $schema['areaServed'] = $service_area;
        }
        $hours = $this->normalize_schema_text((string) get_post_meta($post_id, 'eep_directory_operating_hours', true), 220);
        if ($hours !== '') {
            $schema['openingHours'] = $hours;
        }
        $pricing = $this->normalize_schema_text((string) get_post_meta($post_id, 'eep_directory_pricing_summary', true), 120);
        if ($pricing !== '') {
            $schema['priceRange'] = $pricing;
        }
        $established_year = absint((int) get_post_meta($post_id, 'eep_directory_established_year', true));
        $current_year = (int) gmdate('Y') + 1;
        if ($established_year >= 1800 && $established_year <= $current_year) {
            $schema['foundingDate'] = sprintf('%04d-01-01', $established_year);
        }

        $contact_person = $this->normalize_schema_text((string) get_post_meta($post_id, 'eep_directory_contact_person', true), 120);
        if ($contact_person !== '') {
            $schema['contactPoint'] = [
                '@type' => 'ContactPoint',
                'name' => $contact_person,
                'contactType' => 'customer support',
            ];
            if (is_email($email)) {
                $schema['contactPoint']['email'] = $email;
            }
            if ($phone !== '') {
                $schema['contactPoint']['telephone'] = $phone;
            }
        }

        $type_terms = wp_get_post_terms($post_id, self::TAX_TYPE, ['fields' => 'names']);
        if (!is_wp_error($type_terms) && !empty($type_terms[0])) {
            $schema['category'] = $this->normalize_schema_text((string) $type_terms[0], 100);
        }
        $tag_terms = wp_get_post_terms($post_id, self::TAX_TAG, ['fields' => 'names']);
        if (is_wp_error($tag_terms)) {
            $tag_terms = [];
        }
        $services = $this->parse_delimited_values((string) get_post_meta($post_id, 'eep_directory_services', true));
        $keywords = [];
        foreach (array_merge((array) $tag_terms, (array) $services) as $keyword) {
            $keyword = $this->normalize_schema_text((string) $keyword, 80);
            if ($keyword !== '') {
                $keywords[] = $keyword;
            }
        }
        $keywords = array_values(array_unique($keywords));
        if (!empty($keywords)) {
            $schema['keywords'] = implode(', ', $keywords);
        }

        $document_schema_items = $this->build_supporting_document_schema_items($post_id, 8);
        if (!empty($document_schema_items)) {
            $schema['subjectOf'] = $document_schema_items;
        }

        return $schema;
    }


    private function map_market_condition_schema_url($condition) {
        $condition = strtolower(trim((string) $condition));
        if ($condition === '') {
            return '';
        }
        if (strpos($condition, 'new') !== false) {
            return 'https://schema.org/NewCondition';
        }
        if (strpos($condition, 'refurb') !== false) {
            return 'https://schema.org/RefurbishedCondition';
        }
        if (strpos($condition, 'used') !== false || strpos($condition, 'pre-owned') !== false || strpos($condition, 'preowned') !== false) {
            return 'https://schema.org/UsedCondition';
        }
        return '';
    }


    private function map_market_availability_schema_url($status) {
        $status = sanitize_key((string) $status);
        if (in_array($status, ['sold', 'sold_out', 'out_of_stock'], true)) {
            return 'https://schema.org/OutOfStock';
        }
        if (in_array($status, ['reserved', 'hold'], true)) {
            return 'https://schema.org/LimitedAvailability';
        }
        if (in_array($status, ['expired', 'inactive', 'removed'], true)) {
            return 'https://schema.org/Discontinued';
        }
        if (in_array($status, ['pending', 'draft'], true)) {
            return 'https://schema.org/PreOrder';
        }
        return 'https://schema.org/InStock';
    }


    private function build_marketplace_listing_schema_payload($post_id, array $ui_labels = []) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return [];
        }

        $title = $this->normalize_schema_text(get_the_title($post_id), 180);
        $description = $this->normalize_schema_text((string) (get_post_field('post_excerpt', $post_id) ?: get_post_field('post_content', $post_id)), 2400);
        $permalink = esc_url_raw((string) get_permalink($post_id));
        if ($title === '' || $permalink === '') {
            return [];
        }

        $schema = [
            '@context' => 'https://schema.org',
            '@type' => 'Product',
            'name' => $title,
            'url' => $permalink,
            'mainEntityOfPage' => $permalink,
        ];
        if ($description !== '') {
            $schema['description'] = $description;
        }

        $images = [];
        $cover = $this->get_event_cover_image_url($post_id, 'full', 'hero');
        if ($cover !== '') {
            $images[] = esc_url_raw((string) $cover);
        }
        foreach ((array) $this->get_event_gallery_image_urls($post_id, 'full') as $gallery_url) {
            $gallery_url = esc_url_raw((string) $gallery_url);
            if ($gallery_url !== '') {
                $images[] = $gallery_url;
            }
        }
        $images = array_values(array_unique(array_filter($images)));
        if (!empty($images)) {
            $schema['image'] = $images;
        }

        $sku = $this->normalize_schema_text((string) get_post_meta($post_id, 'eep_market_sku', true), 80);
        if ($sku !== '') {
            $schema['sku'] = $sku;
        }
        $brand = $this->normalize_schema_text((string) get_post_meta($post_id, 'eep_market_brand', true), 100);
        if ($brand !== '') {
            $schema['brand'] = [
                '@type' => 'Brand',
                'name' => $brand,
            ];
        }
        $model = $this->normalize_schema_text((string) get_post_meta($post_id, 'eep_market_model', true), 100);
        if ($model !== '') {
            $schema['model'] = $model;
        }
        $year = absint((int) get_post_meta($post_id, 'eep_market_year', true));
        if ($year >= 1900 && $year <= ((int) gmdate('Y') + 2)) {
            $schema['releaseDate'] = sprintf('%04d-01-01', $year);
        }

        $type_terms = wp_get_post_terms($post_id, self::TAX_TYPE, ['fields' => 'names']);
        if (!is_wp_error($type_terms) && !empty($type_terms[0])) {
            $schema['category'] = $this->normalize_schema_text((string) $type_terms[0], 120);
        }
        $tag_terms = wp_get_post_terms($post_id, self::TAX_TAG, ['fields' => 'names']);
        if (is_wp_error($tag_terms)) {
            $tag_terms = [];
        }
        $keyword_values = [];
        foreach ((array) $tag_terms as $term_name) {
            $term_name = $this->normalize_schema_text((string) $term_name, 80);
            if ($term_name !== '') {
                $keyword_values[] = $term_name;
            }
        }
        $keyword_values = array_values(array_unique($keyword_values));
        if (!empty($keyword_values)) {
            $schema['keywords'] = implode(', ', $keyword_values);
        }

        $external_url = esc_url_raw((string) get_post_meta($post_id, 'eep_external_url', true));
        $listing_status = sanitize_key((string) get_post_meta($post_id, 'eep_market_listing_status', true));
        $condition = $this->normalize_schema_text((string) get_post_meta($post_id, 'eep_market_condition', true), 60);
        $condition_url = $this->map_market_condition_schema_url($condition);
        $price_raw = trim((string) get_post_meta($post_id, 'eep_price', true));
        $quantity = absint((int) get_post_meta($post_id, 'eep_market_quantity', true));
        $expires_raw = trim((string) get_post_meta($post_id, 'eep_market_expires_on', true));
        $expires_ts = $expires_raw !== '' ? strtotime($expires_raw . ' 23:59:00') : false;

        $currency = 'USD';
        if (stripos($price_raw, '£') !== false || stripos($price_raw, 'gbp') !== false) {
            $currency = 'GBP';
        } elseif (stripos($price_raw, '€') !== false || stripos($price_raw, 'eur') !== false) {
            $currency = 'EUR';
        } elseif (stripos($price_raw, 'cad') !== false) {
            $currency = 'CAD';
        } elseif (stripos($price_raw, 'aud') !== false) {
            $currency = 'AUD';
        }
        $price_value = null;
        if ($price_raw !== '' && preg_match('/\d+(?:\.\d+)?/', $price_raw, $price_match)) {
            $price_value = (float) $price_match[0];
        }

        $author_id = (int) get_post_field('post_author', $post_id);
        $author = $author_id > 0 ? get_user_by('id', $author_id) : null;
        $seller_name = $author instanceof WP_User ? $this->normalize_schema_text((string) $author->display_name, 120) : '';
        if ($seller_name === '') {
            $seller_name = sanitize_text_field((string) $this->get_white_label_brand_name());
        }
        $seller_type = sanitize_key((string) get_post_meta($post_id, 'eep_market_seller_type', true));

        $offer = [
            '@type' => 'Offer',
            'url' => $external_url !== '' ? $external_url : $permalink,
            'availability' => $this->map_market_availability_schema_url($listing_status),
            'priceCurrency' => $currency,
            'seller' => [
                '@type' => $seller_type === 'business' ? 'Organization' : 'Person',
                'name' => $seller_name,
            ],
        ];
        if ($price_value !== null) {
            $offer['price'] = $price_value;
        }
        if ($condition_url !== '') {
            $offer['itemCondition'] = $condition_url;
        }
        if ($quantity > 0) {
            $offer['inventoryLevel'] = [
                '@type' => 'QuantitativeValue',
                'value' => $quantity,
            ];
        }
        if ($expires_ts) {
            $offer['priceValidUntil'] = gmdate('Y-m-d', (int) $expires_ts);
        }
        $schema['offers'] = $offer;

        $address = trim((string) get_post_meta($post_id, 'eep_address', true));
        $city = trim((string) get_post_meta($post_id, 'eep_city', true));
        $country = trim((string) get_post_meta($post_id, 'eep_country', true));
        $lat = get_post_meta($post_id, 'eep_lat', true);
        $lng = get_post_meta($post_id, 'eep_lng', true);
        if ($address !== '' || $city !== '' || $country !== '' || (is_numeric($lat) && is_numeric($lng))) {
            $location = [
                '@type' => 'Place',
                'name' => $title,
            ];
            if ($address !== '' || $city !== '' || $country !== '') {
                $location['address'] = [
                    '@type' => 'PostalAddress',
                    'streetAddress' => $this->normalize_schema_text($address, 180),
                    'addressLocality' => $this->normalize_schema_text($city, 100),
                    'addressCountry' => $this->normalize_schema_text($country, 100),
                ];
            }
            if (is_numeric($lat) && is_numeric($lng)) {
                $location['geo'] = [
                    '@type' => 'GeoCoordinates',
                    'latitude' => (float) $lat,
                    'longitude' => (float) $lng,
                ];
            }
            $schema['availableAtOrFrom'] = $location;
        }

        $delivery_methods = $this->parse_delimited_values((string) get_post_meta($post_id, 'eep_market_delivery_methods', true));
        $payment_methods = $this->normalize_schema_text((string) get_post_meta($post_id, 'eep_market_payment_methods', true), 180);
        $returns_accepted = (string) get_post_meta($post_id, 'eep_market_returns_accepted', true) === '1';
        $additional = [];
        if (!empty($delivery_methods)) {
            $delivery_clean = [];
            foreach ((array) $delivery_methods as $method) {
                $method = $this->normalize_schema_text((string) $method, 60);
                if ($method !== '') {
                    $delivery_clean[] = $method;
                }
            }
            if (!empty($delivery_clean)) {
                $additional[] = [
                    '@type' => 'PropertyValue',
                    'name' => __('Delivery Methods', 'nexdirectory'),
                    'value' => implode(', ', array_values(array_unique($delivery_clean))),
                ];
            }
        }
        if ($payment_methods !== '') {
            $additional[] = [
                '@type' => 'PropertyValue',
                'name' => __('Payment Methods', 'nexdirectory'),
                'value' => $payment_methods,
            ];
        }
        $additional[] = [
            '@type' => 'PropertyValue',
            'name' => __('Returns Accepted', 'nexdirectory'),
            'value' => $returns_accepted ? __('Yes', 'nexdirectory') : __('No', 'nexdirectory'),
        ];
        if (!empty($additional)) {
            $schema['additionalProperty'] = $additional;
        }

        $document_schema_items = $this->build_supporting_document_schema_items($post_id, 8);
        if (!empty($document_schema_items)) {
            $schema['subjectOf'] = $document_schema_items;
        }

        return $schema;
    }


    private function get_single_event_seo_meta_value($post_id, $meta_key, $max_length = 0) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return '';
        }
        $value = trim((string) get_post_meta($post_id, (string) $meta_key, true));
        if ($value === '') {
            return '';
        }
        $value = trim((string) wp_strip_all_tags($value));
        if ($value === '') {
            return '';
        }
        $max_length = (int) $max_length;
        if ($max_length > 0) {
            if (function_exists('mb_substr')) {
                $value = mb_substr($value, 0, $max_length);
            } else {
                $value = substr($value, 0, $max_length);
            }
        }
        return trim((string) $value);
    }

    private function get_homepage_seo_option_value($option_key, $max_length = 0) {
        $option_key = sanitize_key((string) $option_key);
        if ($option_key === '') {
            return '';
        }
        $value = trim((string) get_option($option_key, ''));
        if ($value === '') {
            return '';
        }
        $value = trim((string) wp_strip_all_tags($value));
        if ($value === '') {
            return '';
        }
        $max_length = (int) $max_length;
        if ($max_length > 0) {
            if (function_exists('mb_substr')) {
                $value = mb_substr($value, 0, $max_length);
            } else {
                $value = substr($value, 0, $max_length);
            }
        }
        return trim((string) $value);
    }

    public function filter_home_document_title($title) {
        if (is_admin() || !is_front_page()) {
            return $title;
        }
        $custom_title = $this->get_homepage_seo_option_value('eep_home_meta_title', 60);
        if ($custom_title === '') {
            return $title;
        }
        return $custom_title;
    }

    public function output_homepage_meta_tags() {
        if (is_admin() || wp_doing_ajax() || is_feed() || is_trackback()) {
            return;
        }

        $favicon_url = esc_url_raw((string) get_option('eep_style_favicon_url', ''));
        if ($favicon_url !== '') {
            echo '<link rel="icon" href="' . esc_url($favicon_url) . '">' . "\n";
            echo '<link rel="shortcut icon" href="' . esc_url($favicon_url) . '">' . "\n";
            echo '<link rel="apple-touch-icon" href="' . esc_url($favicon_url) . '">' . "\n";
        }

        if (!is_front_page()) {
            return;
        }

        $custom_title = $this->get_homepage_seo_option_value('eep_home_meta_title', 60);
        $custom_description = $this->get_homepage_seo_option_value('eep_home_meta_description', 158);
        if ($custom_title === '' && $custom_description === '') {
            return;
        }

        $social_title = $custom_title !== '' ? $custom_title : sanitize_text_field((string) $this->get_white_label_brand_name());
        $social_description = $custom_description !== '' ? $custom_description : sanitize_text_field((string) get_bloginfo('description'));
        $canonical_url = esc_url_raw((string) home_url('/'));

        if ($custom_description !== '') {
            echo '<meta name="description" content="' . esc_attr($custom_description) . '">' . "\n";
        }
        if ($social_title !== '') {
            echo '<meta property="og:title" content="' . esc_attr($social_title) . '">' . "\n";
            echo '<meta name="twitter:title" content="' . esc_attr($social_title) . '">' . "\n";
        }
        if ($social_description !== '') {
            echo '<meta property="og:description" content="' . esc_attr($social_description) . '">' . "\n";
            echo '<meta name="twitter:description" content="' . esc_attr($social_description) . '">' . "\n";
        }
        if ($canonical_url !== '') {
            echo '<meta property="og:url" content="' . esc_attr($canonical_url) . '">' . "\n";
        }
    }


    public function filter_single_event_document_title($title) {
        if (is_admin() || !is_singular(self::POST_TYPE)) {
            return $title;
        }
        $post_id = absint((int) get_queried_object_id());
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return $title;
        }
        $custom_title = $this->get_single_event_seo_meta_value($post_id, 'eep_meta_title', 60);
        if ($custom_title === '') {
            return $title;
        }
        return $custom_title;
    }


    public function output_single_event_meta_tags() {
        if (is_admin() || wp_doing_ajax() || is_feed() || is_trackback()) {
            return;
        }
        if (!is_singular(self::POST_TYPE)) {
            return;
        }
        $post_id = absint((int) get_queried_object_id());
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            return;
        }

        $custom_title = $this->get_single_event_seo_meta_value($post_id, 'eep_meta_title', 60);
        $custom_description = $this->get_single_event_seo_meta_value($post_id, 'eep_meta_description', 158);
        if ($custom_title === '' && $custom_description === '') {
            return;
        }

        $canonical_url = esc_url_raw((string) get_permalink($post_id));
        $social_title = $custom_title !== '' ? $custom_title : sanitize_text_field((string) get_the_title($post_id));
        $social_description = $custom_description;
        if ($social_description === '') {
            $social_description = trim((string) eep_strip_all_tags((string) (get_post_field('post_excerpt', $post_id) ?: get_post_field('post_content', $post_id))));
            if (function_exists('mb_substr')) {
                $social_description = mb_substr((string) $social_description, 0, 158);
            } else {
                $social_description = substr((string) $social_description, 0, 158);
            }
            $social_description = trim((string) $social_description);
        }

        if ($custom_description !== '') {
            echo '<meta name="description" content="' . esc_attr($custom_description) . '">' . "\n";
        }
        if ($social_title !== '') {
            echo '<meta property="og:title" content="' . esc_attr($social_title) . '">' . "\n";
            echo '<meta name="twitter:title" content="' . esc_attr($social_title) . '">' . "\n";
        }
        if ($social_description !== '') {
            echo '<meta property="og:description" content="' . esc_attr($social_description) . '">' . "\n";
            echo '<meta name="twitter:description" content="' . esc_attr($social_description) . '">' . "\n";
        }
        if ($canonical_url !== '') {
            echo '<meta property="og:url" content="' . esc_attr($canonical_url) . '">' . "\n";
        }
    }


    public function output_event_schema() {
        if (is_admin() || wp_doing_ajax() || is_feed() || is_trackback() || (function_exists('is_robots') && is_robots())) {
            return;
        }

        $organization_schema = $this->build_global_organization_schema();
        $website_schema = $this->build_global_website_schema();
        $this->emit_json_ld_schema((array) $organization_schema);
        $this->emit_json_ld_schema((array) $website_schema);

        $is_single_listing = is_singular(self::POST_TYPE);
        $page_context = $is_single_listing ? '' : $this->detect_platform_schema_page_context();

        $post_id = 0;
        $ui_labels = [];
        if ($is_single_listing) {
            $post_id = absint((int) get_queried_object_id());
            if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE || get_post_status($post_id) !== 'publish') {
                return;
            }
            $resolved_profile_key = $this->resolve_profile_key_for_post($post_id);
            $ui_labels = $this->get_site_profile_ui_labels($resolved_profile_key);
        } else {
            $ui_labels = $this->get_site_profile_ui_labels();
        }

        $breadcrumb_schema = $this->build_platform_breadcrumb_schema($is_single_listing ? get_the_title($post_id) : '');
        if (empty($breadcrumb_schema)) {
            $breadcrumb_schema = $this->build_generic_breadcrumb_schema($is_single_listing ? get_the_title($post_id) : '');
        }
        $this->emit_json_ld_schema($breadcrumb_schema);

        if (!$is_single_listing) {
            $page_schema = $page_context !== ''
                ? $this->build_platform_page_schema($page_context, (array) $ui_labels)
                : $this->build_generic_page_schema();
            $this->emit_json_ld_schema((array) $page_schema);

            if (is_author()) {
                $author_object = get_queried_object();
                if ($author_object instanceof WP_User) {
                    $this->emit_json_ld_schema($this->build_person_schema_from_user($author_object, (string) $this->current_url()));
                } elseif (is_object($author_object) && isset($author_object->ID)) {
                    $this->emit_json_ld_schema($this->build_person_schema_from_user((int) $author_object->ID, (string) $this->current_url()));
                }
            }
            return;
        }

        $this->emit_json_ld_schema((array) $this->build_generic_page_schema());

        $listing_permalink = esc_url_raw((string) get_permalink($post_id));
        $listing_author_id = (int) get_post_field('post_author', $post_id);
        $listing_author = $listing_author_id > 0 ? get_user_by('id', $listing_author_id) : null;
        $listing_author_schema = $listing_author instanceof WP_User
            ? $this->build_person_schema_from_user($listing_author, $listing_permalink)
            : [];
        $this->emit_json_ld_schema((array) $listing_author_schema);

        $is_job_profile = (string) ($ui_labels['is_job_profile'] ?? '0') === '1';
        $is_directory_profile = (string) ($ui_labels['is_directory_profile'] ?? '0') === '1';
        $is_marketplace_profile = (string) ($ui_labels['is_marketplace_profile'] ?? '0') === '1';
        $is_events_profile = (string) ($ui_labels['is_events_profile'] ?? '1') === '1';
        if ($is_job_profile) {
            $title = get_the_title($post_id);
            $description_raw = (string) (get_post_field('post_excerpt', $post_id) ?: get_post_field('post_content', $post_id));
            $description = eep_strip_all_tags($description_raw);
            $permalink = get_permalink($post_id);
            $posted_raw = (string) get_post_field('post_date', $post_id);
            $posted_ts = strtotime($posted_raw);
            $posted_c = $posted_ts ? gmdate('c', (int) $posted_ts) : gmdate('c');

            $company_name = trim((string) get_post_meta($post_id, 'eep_company_name', true));
            $company_website = esc_url_raw((string) get_post_meta($post_id, 'eep_job_company_website', true));
            if ($company_website === '') {
                $company_website = esc_url_raw((string) get_post_meta($post_id, 'eep_external_url', true));
            }
            $job_type = trim((string) get_post_meta($post_id, 'eep_job_type', true));
            $job_work_mode = sanitize_key((string) get_post_meta($post_id, 'eep_job_work_mode', true));
            $job_salary = trim((string) get_post_meta($post_id, 'eep_job_salary', true));
            $job_closing_raw = trim((string) get_post_meta($post_id, 'eep_job_closing_date', true));
            $job_closing_ts = $job_closing_raw !== '' ? strtotime($job_closing_raw . ' 23:59:00') : false;
            $job_apply_url = esc_url_raw((string) get_post_meta($post_id, 'eep_job_apply_url', true));
            $job_apply_email = sanitize_email((string) get_post_meta($post_id, 'eep_job_application_email', true));
            $job_department = trim((string) get_post_meta($post_id, 'eep_job_department', true));
            $job_skills = trim((string) get_post_meta($post_id, 'eep_job_skills', true));
            $job_industry = trim((string) get_post_meta($post_id, 'eep_job_industry', true));

            $address = trim((string) get_post_meta($post_id, 'eep_address', true));
            $city = trim((string) get_post_meta($post_id, 'eep_city', true));
            $country = trim((string) get_post_meta($post_id, 'eep_country', true));
            $venue = trim((string) get_post_meta($post_id, 'eep_venue', true));
            $lat = get_post_meta($post_id, 'eep_lat', true);
            $lng = get_post_meta($post_id, 'eep_lng', true);

            $tag_terms = wp_get_post_terms($post_id, self::TAX_TAG, ['fields' => 'names']);
            $type_terms = wp_get_post_terms($post_id, self::TAX_TYPE, ['fields' => 'names']);
            $skills_terms = array_values(array_unique(array_filter(array_map('eep_sanitize_text', array_merge((array) $tag_terms, (array) $type_terms)))));
            if ($job_skills !== '') {
                $skills_terms[] = eep_sanitize_text($job_skills);
                $skills_terms = array_values(array_unique(array_filter($skills_terms)));
            }

            $organization_name = $company_name;
            if ($organization_name === '') {
                $author_id = (int) get_post_field('post_author', $post_id);
                $organization_name = trim((string) get_the_author_meta('display_name', $author_id));
                if ($organization_name === '') {
                    $organization_name = sanitize_text_field((string) $this->get_white_label_brand_name());
                }
            }

            $schema = [
                '@context' => 'https://schema.org',
                '@type' => 'JobPosting',
                'title' => $title,
                'description' => $description,
                'datePosted' => $posted_c,
                'url' => $permalink,
                'hiringOrganization' => [
                    '@type' => 'Organization',
                    'name' => $organization_name,
                ],
                'identifier' => [
                    '@type' => 'PropertyValue',
                    'name' => sanitize_text_field((string) $this->get_white_label_brand_name()),
                    'value' => (string) $post_id,
                ],
                'directApply' => ($job_apply_url !== '' || is_email($job_apply_email)),
            ];

            if ($company_website !== '') {
                $schema['hiringOrganization']['sameAs'] = $company_website;
            }
            if ($job_type !== '') {
                $schema['employmentType'] = $job_type;
            }
            if ($job_closing_ts) {
                $schema['validThrough'] = gmdate('c', (int) $job_closing_ts);
            }
            if ($job_department !== '') {
                $schema['industry'] = $job_department;
            } elseif ($job_industry !== '') {
                $schema['industry'] = $job_industry;
            }
            if (!empty($skills_terms)) {
                $schema['skills'] = implode(', ', $skills_terms);
            }

            if ($job_work_mode === 'remote') {
                $schema['jobLocationType'] = 'TELECOMMUTE';
                if ($country !== '') {
                    $schema['applicantLocationRequirements'] = [
                        '@type' => 'Country',
                        'name' => $country,
                    ];
                }
            } else {
                if ($job_work_mode === 'hybrid') {
                    $schema['jobLocationType'] = 'HYBRID';
                } elseif ($job_work_mode === 'on-site') {
                    $schema['jobLocationType'] = 'ON_SITE';
                }
                if ($address !== '' || $city !== '' || $country !== '' || $venue !== '') {
                    $location = [
                        '@type' => 'Place',
                        'name' => $venue !== '' ? $venue : $organization_name,
                        'address' => [
                            '@type' => 'PostalAddress',
                            'streetAddress' => $address,
                            'addressLocality' => $city,
                            'addressCountry' => $country,
                        ],
                    ];
                    if (is_numeric($lat) && is_numeric($lng)) {
                        $location['geo'] = [
                            '@type' => 'GeoCoordinates',
                            'latitude' => (float) $lat,
                            'longitude' => (float) $lng,
                        ];
                    }
                    $schema['jobLocation'] = $location;
                }
            }

            if ($job_salary !== '') {
                preg_match_all('/\d+(?:\.\d+)?/', $job_salary, $salary_matches);
                $amounts = isset($salary_matches[0]) ? array_map('floatval', (array) $salary_matches[0]) : [];
                $currency = 'USD';
                if (stripos($job_salary, '£') !== false || stripos($job_salary, 'gbp') !== false) {
                    $currency = 'GBP';
                } elseif (stripos($job_salary, '€') !== false || stripos($job_salary, 'eur') !== false) {
                    $currency = 'EUR';
                } elseif (stripos($job_salary, 'cad') !== false) {
                    $currency = 'CAD';
                } elseif (stripos($job_salary, 'aud') !== false) {
                    $currency = 'AUD';
                }
                if (!empty($amounts)) {
                    $value_payload = [
                        '@type' => 'QuantitativeValue',
                        'currency' => $currency,
                    ];
                    $min_salary = min($amounts);
                    $max_salary = max($amounts);
                    if ($min_salary > 0) {
                        $value_payload['minValue'] = $min_salary;
                    }
                    if ($max_salary > 0) {
                        $value_payload['maxValue'] = $max_salary;
                    }
                    if ($min_salary === $max_salary && $min_salary > 0) {
                        $value_payload['value'] = $min_salary;
                        unset($value_payload['minValue'], $value_payload['maxValue']);
                    }
                    $schema['baseSalary'] = [
                        '@type' => 'MonetaryAmount',
                        'currency' => $currency,
                        'value' => $value_payload,
                    ];
                }
            }

            $this->emit_json_ld_schema($schema);
            return;
        }

        if ($is_directory_profile) {
            $directory_schema = $this->build_directory_listing_schema_payload($post_id, (array) $ui_labels);
            $this->emit_json_ld_schema((array) $directory_schema);
            return;
        }

        if ($is_marketplace_profile) {
            $market_schema = $this->build_marketplace_listing_schema_payload($post_id, (array) $ui_labels);
            $this->emit_json_ld_schema((array) $market_schema);
            return;
        }

        if (!$is_events_profile) {
            return;
        }

        $start_raw = (string) get_post_meta($post_id, 'eep_start', true);
        $end_raw = (string) get_post_meta($post_id, 'eep_end', true);
        $timezone = $this->sanitize_timezone_value((string) get_post_meta($post_id, 'eep_timezone', true));
        if ($timezone === '') {
            $timezone = $this->sanitize_timezone_value(wp_timezone_string());
        }
        if ($timezone === '') {
            $timezone = 'UTC';
        }
        try {
            $event_tz = new DateTimeZone($timezone);
        } catch (Exception $e) {
            $event_tz = new DateTimeZone('UTC');
        }

        $start_dt = null;
        try {
            if ($start_raw !== '') {
                $start_dt = new DateTime($start_raw, $event_tz);
            }
        } catch (Exception $e) {
            $start_dt = null;
        }
        if (!$start_dt instanceof DateTime) {
            $start_unix = strtotime($start_raw);
            if (!$start_unix) {
                return;
            }
            $start_dt = new DateTime('@' . (int) $start_unix);
            $start_dt->setTimezone($event_tz);
        }

        $end_dt = null;
        try {
            if ($end_raw !== '') {
                $end_dt = new DateTime($end_raw, $event_tz);
            }
        } catch (Exception $e) {
            $end_dt = null;
        }
        if (!$end_dt instanceof DateTime || $end_dt <= $start_dt) {
            $end_dt = clone $start_dt;
            $end_dt->modify('+1 hour');
        }

        $start = $start_dt->getTimestamp();
        $end = $end_dt->getTimestamp();
        $address = trim((string) get_post_meta($post_id, 'eep_address', true));
        $city = trim((string) get_post_meta($post_id, 'eep_city', true));
        $country = trim((string) get_post_meta($post_id, 'eep_country', true));
        $venue = trim((string) get_post_meta($post_id, 'eep_venue', true));
        $lat = get_post_meta($post_id, 'eep_lat', true);
        $lng = get_post_meta($post_id, 'eep_lng', true);
        $is_official = $this->get_official_event_flag($post_id);
        $event_price_legacy = trim((string) get_post_meta($post_id, 'eep_price', true));
        $event_price = $this->get_event_price_from_ticket_meta($post_id, $event_price_legacy);
        $external_url = esc_url_raw((string) get_post_meta($post_id, 'eep_external_url', true));
        $stream_url = esc_url_raw((string) get_post_meta($post_id, 'eep_event_stream_url', true));
        $mode_terms = wp_get_post_terms($post_id, self::TAX_MODE, ['fields' => 'names']);
        $tag_terms = wp_get_post_terms($post_id, self::TAX_TAG, ['fields' => 'names']);
        $price_terms = wp_get_post_terms($post_id, self::TAX_PRICE, ['fields' => 'names']);
        $type_terms = wp_get_post_terms($post_id, self::TAX_TYPE, ['fields' => 'names']);
        $video_urls = $this->parse_multiline_urls((string) get_post_meta($post_id, 'eep_video_urls', true));
        $gallery_urls = $this->get_event_gallery_image_urls($post_id, 'thumbnail');
        $rsvp_count = count($this->get_event_attendees_by_status($post_id, 'yes'));
        $capacity = (int) get_post_meta($post_id, 'eep_capacity', true);

        $attendance_mode = 'https://schema.org/MixedEventAttendanceMode';
        $mode_blob = strtolower(implode(' ', array_map('strval', (array) $mode_terms)));
        $looks_online = strpos($mode_blob, 'online') !== false || strpos($mode_blob, 'video') !== false || strpos($mode_blob, 'stream') !== false;
        $looks_offline = strpos($mode_blob, 'in-person') !== false || strpos($mode_blob, 'in person') !== false || strpos($mode_blob, 'offline') !== false;
        if ($looks_online && !$looks_offline) {
            $attendance_mode = 'https://schema.org/OnlineEventAttendanceMode';
        } elseif ($looks_offline && !$looks_online) {
            $attendance_mode = 'https://schema.org/OfflineEventAttendanceMode';
        }

        $event_status = (time() > $end)
            ? 'https://schema.org/EventCompleted'
            : 'https://schema.org/EventScheduled';
        $availability = ($capacity > 0 && $rsvp_count >= $capacity)
            ? 'https://schema.org/SoldOut'
            : 'https://schema.org/InStock';
        $organizer_name = $listing_author_id > 0 ? get_the_author_meta('display_name', $listing_author_id) : '';
        $organizer_name = $this->normalize_schema_text((string) $organizer_name, 140);
        if ($organizer_name === '') {
            $organizer_name = $this->normalize_schema_text((string) $this->get_white_label_brand_name(), 140);
        }
        $organizer_email = $listing_author instanceof WP_User ? sanitize_email((string) $listing_author->user_email) : '';
        $event_name = $this->normalize_schema_text(get_the_title($post_id), 180);
        $event_description = $this->normalize_schema_text(
            (string) (get_post_field('post_excerpt', $post_id) ?: get_post_field('post_content', $post_id)),
            2400
        );
        if ($event_name === '') {
            return;
        }
        $event_url = $listing_permalink;
        if ($event_url === '') {
            $event_url = esc_url_raw((string) get_permalink($post_id));
        }
        if ($event_url === '') {
            return;
        }
        $event_id = $event_url . '#event';

        $organizer_payload = [];
        if (!empty($listing_author_schema['@id'])) {
            $organizer_payload = [
                '@id' => (string) $listing_author_schema['@id'],
            ];
            if ($organizer_name !== '') {
                $organizer_payload['name'] = $organizer_name;
            }
        } else {
            $organizer_payload = [
                '@type' => 'Person',
                'name' => $organizer_name,
            ];
            if (is_email($organizer_email)) {
                $organizer_payload['email'] = $organizer_email;
            }
        }

        $schema = [
            '@context' => 'https://schema.org',
            '@type' => 'Event',
            '@id' => $event_id,
            'name' => $event_name,
            'description' => $event_description,
            'startDate' => $start_dt->format('c'),
            'endDate' => $end_dt->format('c'),
            'eventStatus' => $event_status,
            'eventAttendanceMode' => $attendance_mode,
            'url' => $event_url,
            'mainEntityOfPage' => $event_url,
            'inLanguage' => str_replace('_', '-', get_locale()),
            'isAccessibleForFree' => false,
            'organizer' => $organizer_payload,
        ];
        if (is_email($organizer_email) && isset($schema['organizer']['@id'])) {
            $schema['organizer']['email'] = $organizer_email;
        }

        $speaker_payloads = $this->build_event_speaker_schema_payloads($post_id, $event_url);
        $speaker_schemas = isset($speaker_payloads['schemas']) && is_array($speaker_payloads['schemas'])
            ? array_values((array) $speaker_payloads['schemas'])
            : [];
        $speaker_refs = isset($speaker_payloads['references']) && is_array($speaker_payloads['references'])
            ? array_values((array) $speaker_payloads['references'])
            : [];
        foreach ($speaker_schemas as $speaker_schema) {
            if (is_array($speaker_schema)) {
                $this->emit_json_ld_schema($speaker_schema);
            }
        }
        if (!empty($speaker_refs)) {
            $schema['performer'] = $speaker_refs;
        }

        if ($is_official) {
            $schema['eventSchedule'] = [
                '@type' => 'Schedule',
                'name' => 'Official Event',
            ];
        }

        $keywords = array_values(array_filter(array_map('eep_sanitize_text', (array) $tag_terms)));
        $about_terms = array_values(array_filter(array_map('eep_sanitize_text', array_merge((array) $type_terms, (array) $tag_terms))));
        if (!empty($keywords)) {
            $schema['keywords'] = implode(', ', $keywords);
        }
        if (!empty($about_terms)) {
            $schema['about'] = implode(', ', array_values(array_unique($about_terms)));
        }
        if (!empty($mode_terms)) {
            $schema['eventType'] = eep_sanitize_text(implode(' / ', array_map('strval', (array) $mode_terms)));
        }

        $sub_events = $this->build_event_sub_event_schema_items($post_id, $event_tz, $start_dt, $end_dt);
        if (!empty($sub_events)) {
            $schema['subEvent'] = $sub_events;
        }

        $image_urls = [];
        $cover_image = $this->get_event_cover_image_url($post_id, 'full', 'hero');
        if ($cover_image !== '') {
            $image_urls[] = $cover_image;
        }
        foreach (array_slice((array) $gallery_urls, 0, 12) as $gallery_url) {
            $gallery_url = esc_url_raw((string) $gallery_url);
            if ($gallery_url !== '') {
                $image_urls[] = $gallery_url;
            }
        }
        $image_urls = array_values(array_unique(array_filter($image_urls)));
        if (!empty($image_urls)) {
            $schema['image'] = $image_urls;
        }
        if ($attendance_mode === 'https://schema.org/OnlineEventAttendanceMode' && $stream_url !== '') {
            $schema['location'] = [
                '@type' => 'VirtualLocation',
                'url' => $stream_url,
            ];
        } elseif ($address !== '' || $city !== '' || $country !== '' || $venue !== '') {
            $location = [
                '@type' => 'Place',
                'name' => $venue !== '' ? $venue : $event_name,
                'address' => [
                    '@type' => 'PostalAddress',
                    'streetAddress' => $this->normalize_schema_text($address, 180),
                    'addressLocality' => $this->normalize_schema_text($city, 100),
                    'addressCountry' => $this->normalize_schema_text($country, 100),
                ],
            ];
            if (is_numeric($lat) && is_numeric($lng)) {
                $location['geo'] = [
                    '@type' => 'GeoCoordinates',
                    'latitude' => (float) $lat,
                    'longitude' => (float) $lng,
                ];
            }
            $schema['location'] = $location;
        }

        $offer_url = $external_url !== '' ? $external_url : get_permalink($post_id);
        $offer = [
            '@type' => 'Offer',
            'url' => $offer_url,
            'availability' => $availability,
        ];
        $currency = 'USD';
        if (stripos($event_price, '£') !== false || stripos($event_price, 'gbp') !== false) {
            $currency = 'GBP';
        } elseif (stripos($event_price, '€') !== false || stripos($event_price, 'eur') !== false) {
            $currency = 'EUR';
        } elseif (stripos($event_price, 'cad') !== false) {
            $currency = 'CAD';
        } elseif (stripos($event_price, 'aud') !== false) {
            $currency = 'AUD';
        }
        $is_free = false;
        if ($event_price !== '') {
            if (preg_match('/\d+(?:\.\d+)?/', $event_price, $match)) {
                $offer['price'] = $match[0];
            }
            if (stripos($event_price, 'free') !== false || in_array('Free', (array) $price_terms, true)) {
                $offer['price'] = '0';
                $is_free = true;
            }
            $offer['priceCurrency'] = $currency;
        } elseif (in_array('Free', (array) $price_terms, true)) {
            $offer['price'] = '0';
            $offer['priceCurrency'] = $currency;
            $is_free = true;
        }
        $schema['isAccessibleForFree'] = $is_free;
        $schema['offers'] = $offer;

        if (!empty($video_urls)) {
            $video_payload = [];
            foreach (array_slice((array) $video_urls, 0, 6) as $index => $video_url) {
                $video_url = esc_url_raw((string) $video_url);
                if ($video_url === '') {
                    continue;
                }
                $video_object = [
                    '@type' => 'VideoObject',
                    'name' => sprintf(__('Event video %d', 'nexdirectory'), (int) $index + 1),
                    'url' => $video_url,
                ];
                $youtube_id = $this->extract_youtube_id_from_url($video_url);
                if ($youtube_id !== '') {
                    $video_object['embedUrl'] = 'https://www.youtube.com/embed/' . $youtube_id;
                    $video_object['thumbnailUrl'] = 'https://img.youtube.com/vi/' . $youtube_id . '/hqdefault.jpg';
                }
                $video_payload[] = $video_object;
            }
            if (count($video_payload) === 1) {
                $schema['video'] = $video_payload[0];
            } elseif (!empty($video_payload)) {
                $schema['video'] = $video_payload;
            }
        }

        $document_schema_items = $this->build_supporting_document_schema_items($post_id, 8);
        if (!empty($document_schema_items)) {
            $schema['subjectOf'] = $document_schema_items;
        }

        $this->emit_json_ld_schema($schema);
    }


    public function single_template($single) {
        global $post;
        if ($post && $post->post_type === self::POST_TYPE) {
            $template = plugin_dir_path(EEP_PLUGIN_FILE) . 'templates/single-listing.php';
            if (is_readable($template)) {
                return $template;
            }
        }

        return $single;
    }


    private function extract_youtube_id_from_url($url) {
        $url = trim((string) $url);
        if ($url === '') {
            return '';
        }
        if (preg_match('/^[A-Za-z0-9_-]{11}$/', $url)) {
            return $url;
        }

        $parts = wp_parse_url($url);
        if (!is_array($parts) || empty($parts['host'])) {
            return '';
        }

        $host = strtolower((string) $parts['host']);
        $path = trim((string) ($parts['path'] ?? ''), '/');
        $query = (string) ($parts['query'] ?? '');

        if (strpos($host, 'youtu.be') !== false && $path !== '') {
            $candidate = explode('/', $path)[0];
            return preg_match('/^[A-Za-z0-9_-]{11}$/', $candidate) ? $candidate : '';
        }

        if (strpos($host, 'youtube.com') === false) {
            return '';
        }

        if ($query !== '') {
            parse_str($query, $query_args);
            $candidate = isset($query_args['v']) ? (string) $query_args['v'] : '';
            if (preg_match('/^[A-Za-z0-9_-]{11}$/', $candidate)) {
                return $candidate;
            }
        }

        $segments = $path !== '' ? explode('/', $path) : [];
        if (count($segments) >= 2 && in_array($segments[0], ['embed', 'shorts', 'live', 'v'], true)) {
            $candidate = (string) $segments[1];
            return preg_match('/^[A-Za-z0-9_-]{11}$/', $candidate) ? $candidate : '';
        }

        return '';
    }


    private function build_event_video_cards(array $video_urls, $fallback_image = '') {
        $cards = [];
        $fallback_image = esc_url((string) $fallback_image);
        foreach (array_values($video_urls) as $index => $raw_url) {
            $url = esc_url_raw((string) $raw_url);
            if ($url === '') {
                continue;
            }

            $title = sprintf(__('Video %d', 'nexdirectory'), $index + 1);
            $youtube_id = $this->extract_youtube_id_from_url($url);
            if ($youtube_id !== '') {
                $cards[] = [
                    'title' => $title,
                    'url' => esc_url($url),
                    'thumb' => esc_url('https://img.youtube.com/vi/' . rawurlencode($youtube_id) . '/hqdefault.jpg'),
                    'embed_url' => esc_url('https://www.youtube.com/embed/' . rawurlencode($youtube_id) . '?autoplay=1&rel=0'),
                ];
                continue;
            }

            if ($fallback_image === '') {
                continue;
            }
            $cards[] = [
                'title' => $title,
                'url' => esc_url($url),
                'thumb' => $fallback_image,
                'embed_url' => '',
            ];
        }

        return $cards;
    }


    private function get_supporting_document_embed_url($url) {
        $url = esc_url_raw((string) $url);
        if ($url === '') {
            return '';
        }
        $path = (string) wp_parse_url($url, PHP_URL_PATH);
        $ext = strtolower((string) pathinfo($path, PATHINFO_EXTENSION));
        if ($ext === 'pdf') {
            return esc_url($url);
        }
        return esc_url('https://docs.google.com/gview?embedded=1&url=' . rawurlencode($url));
    }


    private function build_event_supporting_document_cards(array $document_urls) {
        $cards = [];
        foreach (array_values($document_urls) as $index => $raw_url) {
            $url = esc_url_raw((string) $raw_url);
            if ($url === '') {
                continue;
            }
            $path = (string) wp_parse_url($url, PHP_URL_PATH);
            $filename = trim((string) basename($path));
            if ($filename === '') {
                $filename = sprintf(__('Document %d', 'nexdirectory'), $index + 1);
            }
            $extension = strtolower((string) pathinfo($filename, PATHINFO_EXTENSION));
            $label = $filename;
            if (strpos($filename, '.') !== false) {
                $label = (string) preg_replace('/\.[^.]+$/', '', $filename);
            }
            $label = trim((string) str_replace(['-', '_'], ' ', $label));
            if ($label === '') {
                $label = sprintf(__('Document %d', 'nexdirectory'), $index + 1);
            }
            $cards[] = [
                'title' => $label,
                'file_name' => $filename,
                'extension' => $extension !== '' ? strtoupper($extension) : __('FILE', 'nexdirectory'),
                'url' => esc_url($url),
                'embed_url' => $this->get_supporting_document_embed_url($url),
            ];
        }
        return $cards;
    }


    public function append_event_meta_to_single($content) {
        if (!is_singular(self::POST_TYPE) || !in_the_loop() || !is_main_query()) {
            return $content;
        }
        static $is_rendering_event_content = false;
        if ($is_rendering_event_content) {
            return $content;
        }
        $is_rendering_event_content = true;
        try {
            $post_id = get_the_ID();
            $content_html = (string) $content;
            if (trim((string) eep_strip_all_tags((string) $content_html)) === '') {
                $fallback_post_content = (string) get_post_field('post_content', $post_id);
                if ($fallback_post_content !== '') {
                    if (function_exists('do_blocks')) {
                        $fallback_post_content = (string) do_blocks($fallback_post_content);
                    }
                    $content_html = wpautop($fallback_post_content);
                }
            }
            $top_injected_html = '';
            $bottom_injected_html = '';
            $content_html = (string) preg_replace('/\[\/?eep_[^\]]*\]/i', '', $content_html);
            if (strpos($content_html, '[') !== false && strpos($content_html, ']') !== false) {
                $content_html = do_shortcode((string) $content_html);
            }
            $content_html = $this->add_nofollow_to_external_links((string) $content_html);

            $event_title = get_the_title($post_id);
            $excerpt = trim((string) get_the_excerpt($post_id));
            if ($excerpt === '') {
                $excerpt = wp_trim_words(eep_strip_all_tags((string) get_post_field('post_content', $post_id)), 26);
            }
            
        $start_raw = (string) get_post_meta($post_id, 'eep_start', true);
        $end_raw = (string) get_post_meta($post_id, 'eep_end', true);
        $start_ts = $start_raw !== '' ? strtotime($start_raw) : false;
        $end_ts = $end_raw !== '' ? strtotime($end_raw) : false;
        $start_has_time = $this->event_datetime_has_time($start_raw);
        $end_has_time = $this->event_datetime_has_time($end_raw);
        $start_display = $this->format_event_datetime_for_display($start_raw);
        $end_display = $this->format_event_datetime_for_display($end_raw);
        $timezone = (string) get_post_meta($post_id, 'eep_timezone', true);
        $venue = trim((string) preg_replace('/\s+/u', ' ', (string) get_post_meta($post_id, 'eep_venue', true)));
        $address = trim((string) preg_replace('/\s+/u', ' ', (string) get_post_meta($post_id, 'eep_address', true)));
        $city = trim((string) preg_replace('/\s+/u', ' ', (string) get_post_meta($post_id, 'eep_city', true)));
        $country = trim((string) preg_replace('/\s+/u', ' ', (string) get_post_meta($post_id, 'eep_country', true)));
        $lat = (string) get_post_meta($post_id, 'eep_lat', true);
        $lng = (string) get_post_meta($post_id, 'eep_lng', true);
        $capacity = (string) get_post_meta($post_id, 'eep_capacity', true);
        $price = (string) get_post_meta($post_id, 'eep_price', true);
        $price_simple = (string) get_post_meta($post_id, 'eep_price_simple', true);
        $contact_email = (string) get_post_meta($post_id, 'eep_contact_email', true);
        $contact_phone = (string) get_post_meta($post_id, 'eep_contact_phone', true);
        $external_url = (string) get_post_meta($post_id, 'eep_external_url', true);
        $external_click_metric = 'click_website';
        if ($external_url !== '' && preg_match('/(?:wa\.me|api\.whatsapp\.com|whatsapp)/i', (string) $external_url)) {
            $external_click_metric = 'click_whatsapp';
        }
        $stream_url = (string) get_post_meta($post_id, 'eep_event_stream_url', true);
        $youtube_one = (string) get_post_meta($post_id, 'eep_youtube_video_id_1', true);
        $youtube_two = (string) get_post_meta($post_id, 'eep_youtube_video_id_2', true);
        $recurrence = (string) get_post_meta($post_id, 'eep_recurrence', true);
        $recurrence_until = (string) get_post_meta($post_id, 'eep_recurrence_until', true);
        $recurrence_until_ts = $recurrence_until !== '' ? strtotime($recurrence_until) : false;
        $recurrence_until_display = $recurrence_until_ts ? wp_date(get_option('date_format'), $recurrence_until_ts) : $recurrence_until;
        $official = $this->get_official_event_flag($post_id);
        $classification_labels = $this->get_classification_labels();
        $resolved_profile_key = $this->resolve_profile_key_for_post($post_id);
        $ui_labels = $this->get_site_profile_ui_labels($resolved_profile_key);
        $profile_singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Listing', 'nexdirectory')));
        $profile_singular_lower = function_exists('mb_strtolower')
            ? mb_strtolower($profile_singular_label, 'UTF-8')
            : strtolower($profile_singular_label);
        $is_events_profile = (string) ($ui_labels['is_events_profile'] ?? '1') === '1';
        $is_directory_profile = (string) ($ui_labels['is_directory_profile'] ?? '0') === '1';
        $is_job_profile = (string) ($ui_labels['is_job_profile'] ?? '0') === '1';
        $is_marketplace_profile = (string) ($ui_labels['is_marketplace_profile'] ?? '0') === '1';
        if ($is_events_profile) {
            $price = $this->get_event_price_from_ticket_meta($post_id, $price);
        }
        $single_visit_site_cta = sanitize_text_field((string) ($ui_labels['single_visit_site_cta'] ?? __('Visit Event Site', 'nexdirectory')));
        $single_apply_email_cta = sanitize_text_field((string) ($ui_labels['single_apply_email_cta'] ?? __('Apply by Email', 'nexdirectory')));
        $single_rsvp_join = sanitize_text_field((string) ($ui_labels['single_rsvp_join'] ?? __('RSVP / Join Waitlist', 'nexdirectory')));
        $single_rsvp_cancel = sanitize_text_field((string) ($ui_labels['single_rsvp_cancel'] ?? __('Cancel RSVP', 'nexdirectory')));
        $single_login_rsvp = sanitize_text_field((string) ($ui_labels['single_login_rsvp'] ?? __('Log In To RSVP', 'nexdirectory')));
        $single_owner_items_cta = sanitize_text_field((string) ($ui_labels['single_owner_items_cta'] ?? __('View all organizer events', 'nexdirectory')));
        $single_timeline_heading = sanitize_text_field((string) ($ui_labels['single_timeline_heading'] ?? __('A Day Of Action', 'nexdirectory')));
        $single_overview_heading = sanitize_text_field((string) ($ui_labels['single_overview_heading'] ?? __('Event Overview', 'nexdirectory')));
        $single_gallery_heading = sanitize_text_field((string) ($ui_labels['single_gallery_heading'] ?? __('Event Gallery', 'nexdirectory')));
        $single_videos_heading = sanitize_text_field((string) ($ui_labels['single_videos_heading'] ?? __('Event Videos', 'nexdirectory')));
        $single_cta_heading = sanitize_text_field((string) ($ui_labels['single_cta_heading'] ?? __('Secure Your Spot', 'nexdirectory')));
        $single_cta_copy = sanitize_text_field((string) ($ui_labels['single_cta_copy'] ?? __('Join the community and reserve your place in this event.', 'nexdirectory')));
        $single_report_cta = sanitize_text_field((string) ($ui_labels['single_report_cta'] ?? __('Report Event', 'nexdirectory')));
        $single_report_placeholder = sanitize_text_field((string) ($ui_labels['single_report_placeholder'] ?? __('Tell us what is wrong with this listing.', 'nexdirectory')));
        $single_contact_login = sanitize_text_field((string) ($ui_labels['single_contact_login'] ?? __('Please log in to contact the event holder.', 'nexdirectory')));
        $single_edit_cta = sanitize_text_field((string) ($ui_labels['single_edit_cta'] ?? __('Edit Event', 'nexdirectory')));
        $single_type_label = sanitize_text_field((string) ($ui_labels['type_label'] ?? __('Event Type', 'nexdirectory')));
        $single_mode_label = sanitize_text_field((string) ($ui_labels['mode_label'] ?? __('Mode', 'nexdirectory')));
        $single_price_range_label = sanitize_text_field((string) ($ui_labels['single_price_range_label'] ?? __('Price Range', 'nexdirectory')));
        $single_classification_label = sanitize_text_field((string) ($ui_labels['single_classification_label'] ?? __('Classification', 'nexdirectory')));
        $single_start_label = sanitize_text_field((string) ($ui_labels['single_start_label'] ?? __('Event Start Date', 'nexdirectory')));
        $single_end_label = sanitize_text_field((string) ($ui_labels['single_end_label'] ?? __('Event End Date', 'nexdirectory')));
        $single_timezone_label = sanitize_text_field((string) ($ui_labels['single_timezone_label'] ?? __('Timezone', 'nexdirectory')));
        $single_pattern_label = sanitize_text_field((string) ($ui_labels['single_pattern_label'] ?? __('Recurring Pattern', 'nexdirectory')));
        $single_until_label = sanitize_text_field((string) ($ui_labels['single_until_label'] ?? __('Recurs Until', 'nexdirectory')));
        $single_price_label = sanitize_text_field((string) ($ui_labels['single_price_label'] ?? __('Price', 'nexdirectory')));
        $single_capacity_label = sanitize_text_field((string) ($ui_labels['single_capacity_label'] ?? __('Capacity', 'nexdirectory')));
        $single_tags_label = sanitize_text_field((string) ($ui_labels['single_tags_label'] ?? __('Tags', 'nexdirectory')));
        $single_phone_label = sanitize_text_field((string) ($ui_labels['single_phone_label'] ?? __('Phone Number', 'nexdirectory')));
        $single_contact_email_label = sanitize_text_field((string) ($ui_labels['single_contact_email_label'] ?? __('Contact Email', 'nexdirectory')));
        $single_website_label = sanitize_text_field((string) ($ui_labels['single_website_label'] ?? __('Event Website', 'nexdirectory')));
        $single_video_label = sanitize_text_field((string) ($ui_labels['single_video_label'] ?? __('Video Stream URL', 'nexdirectory')));
        $single_directory_book_cta = sanitize_text_field((string) ($ui_labels['single_directory_book_cta'] ?? __('Book Appointment', 'nexdirectory')));
        $single_directory_whatsapp_cta = sanitize_text_field((string) ($ui_labels['single_directory_whatsapp_cta'] ?? __('Message on WhatsApp', 'nexdirectory')));
        $company_name = trim((string) get_post_meta($post_id, 'eep_company_name', true));
        $job_type = trim((string) get_post_meta($post_id, 'eep_job_type', true));
        $job_work_mode = trim((string) get_post_meta($post_id, 'eep_job_work_mode', true));
        $job_salary = trim((string) get_post_meta($post_id, 'eep_job_salary', true));
        $job_apply_url = esc_url_raw((string) get_post_meta($post_id, 'eep_job_apply_url', true));
        $job_application_email = sanitize_email((string) get_post_meta($post_id, 'eep_job_application_email', true));
        $job_closing_date_raw = trim((string) get_post_meta($post_id, 'eep_job_closing_date', true));
        $job_closing_ts = $job_closing_date_raw !== '' ? strtotime($job_closing_date_raw . ' 23:59:00') : false;
        $job_closing_display = $job_closing_ts ? wp_date(get_option('date_format'), (int) $job_closing_ts) : '';
        $job_department = trim((string) get_post_meta($post_id, 'eep_job_department', true));
        $job_experience_level = trim((string) get_post_meta($post_id, 'eep_job_experience_level', true));
        $job_reporting_to = trim((string) get_post_meta($post_id, 'eep_job_reporting_to', true));
        $job_remote_policy = trim((string) get_post_meta($post_id, 'eep_job_remote_policy', true));
        $job_visa_sponsorship = (string) get_post_meta($post_id, 'eep_job_visa_sponsorship', true) === '1';
        $job_open_positions = absint((int) get_post_meta($post_id, 'eep_job_open_positions', true));
        $job_interview_process = trim((string) get_post_meta($post_id, 'eep_job_interview_process', true));
        $job_expected_start_date_raw = trim((string) get_post_meta($post_id, 'eep_job_expected_start_date', true));
        $job_expected_start_ts = $job_expected_start_date_raw !== '' ? strtotime($job_expected_start_date_raw . ' 00:00:00') : false;
        $job_expected_start_display = $job_expected_start_ts ? wp_date(get_option('date_format'), (int) $job_expected_start_ts) : '';
        $job_skills = trim((string) get_post_meta($post_id, 'eep_job_skills', true));
        $job_responsibilities = trim((string) get_post_meta($post_id, 'eep_job_responsibilities', true));
        $job_requirements = trim((string) get_post_meta($post_id, 'eep_job_requirements', true));
        $job_benefits = trim((string) get_post_meta($post_id, 'eep_job_benefits', true));
        $job_company_website = esc_url_raw((string) get_post_meta($post_id, 'eep_job_company_website', true));
        $job_company_size = trim((string) get_post_meta($post_id, 'eep_job_company_size', true));
        $job_industry = trim((string) get_post_meta($post_id, 'eep_job_industry', true));
        $directory_business_type = sanitize_key((string) get_post_meta($post_id, 'eep_directory_business_type', true));
        $directory_listing_status = sanitize_key((string) get_post_meta($post_id, 'eep_directory_listing_status', true));
        $directory_contact_person = trim((string) get_post_meta($post_id, 'eep_directory_contact_person', true));
        $directory_service_area = trim((string) get_post_meta($post_id, 'eep_directory_service_area', true));
        $directory_operating_hours = trim((string) get_post_meta($post_id, 'eep_directory_operating_hours', true));
        $directory_response_time = trim((string) get_post_meta($post_id, 'eep_directory_response_time', true));
        $directory_established_year = absint((int) get_post_meta($post_id, 'eep_directory_established_year', true));
        $directory_pricing_summary = trim((string) get_post_meta($post_id, 'eep_directory_pricing_summary', true));
        $directory_appointment_url = esc_url_raw((string) get_post_meta($post_id, 'eep_directory_appointment_url', true));
        $directory_whatsapp_url = esc_url_raw((string) get_post_meta($post_id, 'eep_directory_whatsapp_url', true));
        $directory_services = trim((string) get_post_meta($post_id, 'eep_directory_services', true));
        $directory_highlights = trim((string) get_post_meta($post_id, 'eep_directory_highlights', true));
        $directory_certifications = trim((string) get_post_meta($post_id, 'eep_directory_certifications', true));
        $market_condition = trim((string) get_post_meta($post_id, 'eep_market_condition', true));
        $market_price_type = sanitize_key((string) get_post_meta($post_id, 'eep_market_price_type', true));
        $market_quantity = absint((int) get_post_meta($post_id, 'eep_market_quantity', true));
        $market_sku = trim((string) get_post_meta($post_id, 'eep_market_sku', true));
        $market_brand = trim((string) get_post_meta($post_id, 'eep_market_brand', true));
        $market_model = trim((string) get_post_meta($post_id, 'eep_market_model', true));
        $market_year = absint((int) get_post_meta($post_id, 'eep_market_year', true));
        $market_seller_type = sanitize_key((string) get_post_meta($post_id, 'eep_market_seller_type', true));
        $market_listing_status = sanitize_key((string) get_post_meta($post_id, 'eep_market_listing_status', true));
        $market_expires_on = trim((string) get_post_meta($post_id, 'eep_market_expires_on', true));
        $market_expires_ts = $market_expires_on !== '' ? strtotime($market_expires_on . ' 23:59:00') : false;
        $market_expires_display = $market_expires_ts ? wp_date(get_option('date_format'), (int) $market_expires_ts) : '';
        $market_delivery_methods = preg_split('/[\s,]+/', (string) get_post_meta($post_id, 'eep_market_delivery_methods', true));
        if (!is_array($market_delivery_methods)) {
            $market_delivery_methods = [];
        }
        $market_delivery_methods = array_values(array_unique(array_filter(array_map('sanitize_key', $market_delivery_methods))));
        $market_returns_accepted = (string) get_post_meta($post_id, 'eep_market_returns_accepted', true) === '1';
        $market_accept_offers = (string) get_post_meta($post_id, 'eep_market_accept_offers', true) !== '0';
        $market_payment_methods = trim((string) get_post_meta($post_id, 'eep_market_payment_methods', true));
        $market_condition_notes = trim((string) get_post_meta($post_id, 'eep_market_condition_notes', true));
        $market_shipping_notes = trim((string) get_post_meta($post_id, 'eep_market_shipping_notes', true));
        if ($is_job_profile && $contact_email === '' && $job_application_email !== '') {
            $contact_email = $job_application_email;
        }
        if ($is_job_profile) {
            $single_visit_site_cta = __('Apply Now', 'nexdirectory');
            $single_apply_email_cta = __('Apply by Email', 'nexdirectory');
            $single_rsvp_join = __('Save Job', 'nexdirectory');
            $single_rsvp_cancel = __('Remove Saved Job', 'nexdirectory');
            $single_login_rsvp = __('Log In To Save Job', 'nexdirectory');
            $single_owner_items_cta = __('View all poster listing', 'nexdirectory');
            $single_timeline_heading = __('Application Timeline', 'nexdirectory');
            $single_overview_heading = __('Job Overview', 'nexdirectory');
            $single_gallery_heading = __('Company Gallery', 'nexdirectory');
            $single_videos_heading = __('Team & Culture', 'nexdirectory');
            $single_cta_heading = __('Apply For This Job', 'nexdirectory');
            $single_cta_copy = __('Review the role details and submit your application directly to the employer.', 'nexdirectory');
            $single_report_cta = __('Report Job Listing', 'nexdirectory');
            $single_contact_login = __('Please log in to contact the listing owner.', 'nexdirectory');
            $single_edit_cta = __('Edit Job', 'nexdirectory');
            $single_type_label = __('Department', 'nexdirectory');
            $single_mode_label = __('Location Type', 'nexdirectory');
            $single_price_range_label = __('Salary Range', 'nexdirectory');
            $single_classification_label = __('Employer Verification', 'nexdirectory');
            $single_start_label = __('Posted Date', 'nexdirectory');
            $single_end_label = __('Closing Date', 'nexdirectory');
            $single_pattern_label = __('Application Window', 'nexdirectory');
            $single_until_label = __('Apply Before', 'nexdirectory');
            $single_price_label = __('Salary', 'nexdirectory');
            $single_capacity_label = __('Remote Policy', 'nexdirectory');
            $single_tags_label = __('Skills / Keywords', 'nexdirectory');
            $single_phone_label = __('Phone', 'nexdirectory');
            $single_contact_email_label = __('Contact Email', 'nexdirectory');
            $single_website_label = __('Company Website', 'nexdirectory');
            $single_video_label = __('Company Video URL', 'nexdirectory');
            $single_status_label = __('Application Status', 'nexdirectory');
            $single_contact_section_heading = __('Apply', 'nexdirectory');
            $single_documents_heading = __('Job Documents', 'nexdirectory');
            $single_date_meta_label = __('Apply By', 'nexdirectory');
            $single_location_meta_label = __('Workplace', 'nexdirectory');
            $single_location_heading = __('Work Location', 'nexdirectory');
            $single_open_map_cta = __('View Workplace Map', 'nexdirectory');
        } elseif ($is_marketplace_profile) {
            $single_visit_site_cta = __('View Listing', 'nexdirectory');
            $single_owner_items_cta = __('View all seller listings', 'nexdirectory');
            $single_timeline_heading = '';
            $single_overview_heading = __('Overview', 'nexdirectory');
            $single_gallery_heading = __('Gallery', 'nexdirectory');
            $single_videos_heading = __('Videos', 'nexdirectory');
            $single_cta_heading = __('Contact Seller', 'nexdirectory');
            $single_cta_copy = __('Review the listing details and contact the seller directly.', 'nexdirectory');
            $single_report_cta = __('Report Listing', 'nexdirectory');
            $single_contact_login = __('Please log in to contact the seller.', 'nexdirectory');
            $single_edit_cta = __('Edit Listing', 'nexdirectory');
            $single_type_label = __('Listing Category', 'nexdirectory');
            $single_mode_label = __('Delivery', 'nexdirectory');
            $single_price_range_label = __('Price Type', 'nexdirectory');
            $single_classification_label = __('Seller Type', 'nexdirectory');
            $single_start_label = __('Listed Date', 'nexdirectory');
            $single_end_label = __('Expiry Date', 'nexdirectory');
            $single_pattern_label = __('Availability', 'nexdirectory');
            $single_until_label = __('Available Until', 'nexdirectory');
            $single_price_label = __('Asking Price', 'nexdirectory');
            $single_capacity_label = __('Quantity', 'nexdirectory');
            $single_tags_label = __('Keywords', 'nexdirectory');
            $single_phone_label = __('Seller Phone', 'nexdirectory');
            $single_contact_email_label = __('Seller Email', 'nexdirectory');
            $single_website_label = __('External Listing URL', 'nexdirectory');
            $single_video_label = __('Listing Video URL', 'nexdirectory');
            $single_status_label = __('Listing Status', 'nexdirectory');
            $single_contact_section_heading = __('Message Seller', 'nexdirectory');
            $single_documents_heading = __('Documents', 'nexdirectory');
            $single_date_meta_label = __('Listed', 'nexdirectory');
            $single_location_meta_label = __('Location', 'nexdirectory');
            $single_location_heading = __('Location', 'nexdirectory');
            $single_open_map_cta = __('Open Map', 'nexdirectory');
        } elseif ($is_directory_profile) {
            $single_visit_site_cta = __('Visit Website', 'nexdirectory');
            $single_owner_items_cta = __('View all owner listings', 'nexdirectory');
            $single_timeline_heading = '';
            $single_overview_heading = __('Overview', 'nexdirectory');
            $single_gallery_heading = __('Gallery', 'nexdirectory');
            $single_videos_heading = __('Videos', 'nexdirectory');
            $single_cta_heading = __('Contact Owner', 'nexdirectory');
            $single_cta_copy = __('Review this listing and contact the owner for the latest details.', 'nexdirectory');
            $single_report_cta = __('Report Listing', 'nexdirectory');
            $single_contact_login = __('Please log in to contact the listing owner.', 'nexdirectory');
            $single_edit_cta = __('Edit Listing', 'nexdirectory');
            $single_type_label = __('Listing Category', 'nexdirectory');
            $single_mode_label = __('Listing Status', 'nexdirectory');
            $single_price_range_label = __('Pricing', 'nexdirectory');
            $single_classification_label = __('Business Type', 'nexdirectory');
            $single_start_label = __('Listed Date', 'nexdirectory');
            $single_end_label = __('Updated Date', 'nexdirectory');
            $single_pattern_label = __('Availability', 'nexdirectory');
            $single_until_label = __('Available Until', 'nexdirectory');
            $single_price_label = __('Rate / Price', 'nexdirectory');
            $single_capacity_label = __('Service Area', 'nexdirectory');
            $single_tags_label = __('Keywords', 'nexdirectory');
            $single_phone_label = __('Phone', 'nexdirectory');
            $single_contact_email_label = __('Contact Email', 'nexdirectory');
            $single_website_label = __('Website', 'nexdirectory');
            $single_video_label = __('Video URL', 'nexdirectory');
            $single_status_label = __('Listing Status', 'nexdirectory');
            $single_contact_section_heading = __('Contact Owner', 'nexdirectory');
            $single_documents_heading = __('Documents', 'nexdirectory');
            $single_date_meta_label = __('Updated', 'nexdirectory');
            $single_location_meta_label = __('Location', 'nexdirectory');
            $single_location_heading = __('Location', 'nexdirectory');
            $single_open_map_cta = __('Open Map', 'nexdirectory');
        }
        $type_term_objects = wp_get_post_terms($post_id, self::TAX_TYPE, ['fields' => 'all']);
        if (is_wp_error($type_term_objects)) {
            $type_term_objects = [];
        }
        $mode_term_objects = wp_get_post_terms($post_id, self::TAX_MODE, ['fields' => 'all']);
        if (is_wp_error($mode_term_objects)) {
            $mode_term_objects = [];
        }
        $price_term_objects = wp_get_post_terms($post_id, self::TAX_PRICE, ['fields' => 'all']);
        if (is_wp_error($price_term_objects)) {
            $price_term_objects = [];
        }
        $tag_term_objects = wp_get_post_terms($post_id, self::TAX_TAG, ['fields' => 'all']);
        if (is_wp_error($tag_term_objects)) {
            $tag_term_objects = [];
        }
        $type_terms = array_map(static function ($term) {
            return (string) ($term->name ?? '');
        }, (array) $type_term_objects);
        $mode_terms = array_map(static function ($term) {
            return (string) ($term->name ?? '');
        }, (array) $mode_term_objects);
        $price_terms = array_map(static function ($term) {
            return (string) ($term->name ?? '');
        }, (array) $price_term_objects);
        $tag_terms = array_map(static function ($term) {
            return (string) ($term->name ?? '');
        }, (array) $tag_term_objects);
        $type_names = implode(', ', array_filter($type_terms));
        $mode_names = implode(', ', array_filter($mode_terms));
        $price_names = implode(', ', array_filter($price_terms));
        $tag_names = implode(', ', array_filter($tag_terms));
        $custom_form_fields = $this->get_post_custom_field_schema($post_id, true);
        $custom_field_values = $this->get_post_custom_field_values($post_id, $custom_form_fields);
        $custom_fields_display = [];
        foreach ($custom_form_fields as $field) {
            $field = is_array($field) ? $field : [];
            if ((string) ($field['show_in_event'] ?? '1') !== '1') {
                continue;
            }
            $field_id = sanitize_key((string) ($field['id'] ?? ''));
            if ($field_id === '') {
                continue;
            }
            $type = sanitize_key((string) ($field['type'] ?? 'text'));
            $label = sanitize_text_field((string) ($field['label'] ?? $field_id));
            if ($is_job_profile || $is_marketplace_profile || $is_directory_profile) {
                $label_lc = function_exists('mb_strtolower') ? mb_strtolower($label, 'UTF-8') : strtolower($label);
                if (
                    in_array($field_id, ['lead_sponsor', 'primary_partner', 'speaker_notes', 'owner_notes'], true)
                    || strpos($label_lc, 'owner notes') !== false
                    || strpos($label_lc, 'primary partner') !== false
                    || strpos($label_lc, 'event type') !== false
                    || strpos($label_lc, 'price range') !== false
                    || strpos($label_lc, 'classification') !== false
                    || strpos($label_lc, 'event start') !== false
                    || strpos($label_lc, 'event end') !== false
                    || strpos($label_lc, 'video stream') !== false
                    || strpos($label_lc, 'event website') !== false
                    || strpos($label_lc, 'timezone') !== false
                    || strpos($label_lc, 'capacity') !== false
                    || strpos($label_lc, 'a day of action') !== false
                    || strpos($label_lc, 'itinerary') !== false
                ) {
                    continue;
                }
            }
            $raw_value = isset($custom_field_values[$field_id]) ? $custom_field_values[$field_id] : '';
            if ($type === 'checkbox') {
                if ((string) $raw_value !== '1') {
                    continue;
                }
                $custom_fields_display[] = [
                    'label' => $label,
                    'render_type' => 'text',
                    'value' => __('Yes', 'nexdirectory'),
                ];
                continue;
            }
            if ($type === 'gallery') {
                $gallery_items = $this->parse_multiline_urls((string) $raw_value);
                if (empty($gallery_items)) {
                    continue;
                }
                $custom_fields_display[] = [
                    'label' => $label,
                    'render_type' => 'gallery',
                    'gallery' => $gallery_items,
                ];
                continue;
            }
            if ($type === 'image') {
                $image_url = esc_url((string) $raw_value);
                if ($image_url === '') {
                    continue;
                }
                $custom_fields_display[] = [
                    'label' => $label,
                    'render_type' => 'image',
                    'value' => $image_url,
                ];
                continue;
            }
            if ($type === 'file' || $type === 'url' || $type === 'email') {
                $value = trim((string) $raw_value);
                if ($value === '') {
                    continue;
                }
                $url = $type === 'email' ? 'mailto:' . sanitize_email($value) : esc_url_raw($value);
                if ($url === '') {
                    continue;
                }
                $custom_fields_display[] = [
                    'label' => $label,
                    'render_type' => 'link',
                    'value' => $value,
                    'url' => $url,
                ];
                continue;
            }
            if ($type === 'wysiwyg') {
                $value = trim((string) $raw_value);
                if ($value === '') {
                    continue;
                }
                $custom_fields_display[] = [
                    'label' => $label,
                    'render_type' => 'html',
                    'value' => $this->add_nofollow_to_external_links((string) wp_kses_post($value)),
                ];
                continue;
            }
            if ($type === 'date') {
                $value_ts = strtotime((string) $raw_value);
                $value = $value_ts ? wp_date(get_option('date_format'), $value_ts) : sanitize_text_field((string) $raw_value);
                if ($value === '') {
                    continue;
                }
                $custom_fields_display[] = [
                    'label' => $label,
                    'render_type' => 'text',
                    'value' => $value,
                ];
                continue;
            }
            if ($type === 'datetime-local') {
                $value_ts = strtotime((string) $raw_value);
                $value = $value_ts ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), $value_ts) : sanitize_text_field((string) $raw_value);
                if ($value === '') {
                    continue;
                }
                $custom_fields_display[] = [
                    'label' => $label,
                    'render_type' => 'text',
                    'value' => $value,
                ];
                continue;
            }
            $value = trim((string) preg_replace('/\s+/u', ' ', (string) $raw_value));
            if ($value === '') {
                continue;
            }
            $custom_fields_display[] = [
                'label' => $label,
                'render_type' => 'text',
                'value' => $value,
            ];
        }

        $gallery_urls = $this->parse_multiline_urls((string) get_post_meta($post_id, 'eep_gallery_urls', true));
        $video_urls = $this->parse_multiline_urls((string) get_post_meta($post_id, 'eep_video_urls', true));
        $supporting_doc_urls = $this->parse_multiline_urls((string) get_post_meta($post_id, 'eep_supporting_docs', true));
        if ($youtube_one !== '') {
            $video_urls[] = 'https://www.youtube.com/watch?v=' . $youtube_one;
        }
        if ($youtube_two !== '') {
            $video_urls[] = 'https://www.youtube.com/watch?v=' . $youtube_two;
        }
        if ($stream_url !== '') {
            $video_urls[] = $stream_url;
        }
        $video_urls = array_values(array_unique(array_filter($video_urls)));
        $gallery_display_urls = array_values(array_filter(array_map('esc_url', $gallery_urls), static function ($url) {
            return trim((string) $url) !== '';
        }));

        $cover_image = $this->get_event_cover_image_url($post_id, 'full', 'hero');
        if ($cover_image !== '') {
            $normalized_cover = untrailingslashit($cover_image);
            $gallery_display_urls = array_values(array_filter($gallery_display_urls, static function ($url) use ($normalized_cover) {
                return untrailingslashit((string) $url) !== $normalized_cover;
            }));
        }

        $location_bits = array_filter([$venue, $city, $country], static function ($value) {
            return trim((string) $value) !== '';
        });
        $location_line = trim((string) preg_replace('/\s+/u', ' ', implode(', ', $location_bits)));
        $location_full = trim((string) preg_replace('/\s+/u', ' ', implode(', ', array_filter([$venue, $address, $city, $country], static function ($value) {
            return trim((string) $value) !== '';
        }))));
        $map_link = '';
        if (is_numeric($lat) && is_numeric($lng)) {
            $map_link = eep_add_query_arg([
                'api' => '1',
                'query' => $lat . ',' . $lng,
            ], 'https://www.google.com/maps/search/');
        } elseif ($location_full !== '') {
            $map_link = eep_add_query_arg([
                'api' => '1',
                'query' => $location_full,
            ], 'https://www.google.com/maps/search/');
        }
        $map_embed_url = '';
        if (is_numeric($lat) && is_numeric($lng)) {
            $map_embed_url = 'https://maps.google.com/maps?q=' . rawurlencode($lat . ',' . $lng) . '&z=14&output=embed';
        } elseif ($location_full !== '') {
            $map_embed_url = 'https://maps.google.com/maps?q=' . rawurlencode($location_full) . '&z=14&output=embed';
        }
        $mode_value = $mode_names !== '' ? $mode_names : ($is_events_profile ? __('In-person Event', 'nexdirectory') : __('Standard', 'nexdirectory'));
        $price_value = $price_simple !== '' ? ucfirst($price_simple) : $price_names;
        if ($is_events_profile && $price !== '') {
            $price_value = $price;
        }
        $events_base_url = $this->get_platform_page_url(
            'eep_page_events_url',
            $this->get_profile_route_slugs('events', $resolved_profile_key),
            $this->get_profile_route_default_url('events', $resolved_profile_key)
        );
        $overview_chips = [];
        $seen_chip_labels = [];
        $add_overview_chip = static function ($label, $url, $primary = false) use (&$overview_chips, &$seen_chip_labels) {
            $clean_label = trim((string) $label);
            $clean_url = esc_url_raw((string) $url);
            if ($clean_label === '' || $clean_url === '') {
                return;
            }
            $hash = strtolower($clean_label);
            if (isset($seen_chip_labels[$hash])) {
                return;
            }
            $seen_chip_labels[$hash] = true;
            $overview_chips[] = [
                'label' => $clean_label,
                'url' => $clean_url,
                'primary' => $primary ? '1' : '0',
            ];
        };
        if ($is_events_profile) {
            $add_overview_chip(
                $official ? (string) ($classification_labels['official'] ?? __('Official Event', 'nexdirectory')) : (string) ($classification_labels['unofficial'] ?? __('Unofficial Event', 'nexdirectory')),
                eep_add_query_arg('official', $official ? '1' : '0', $events_base_url),
                true
            );
        }
        foreach ((array) $mode_term_objects as $term_obj) {
            if (!$term_obj instanceof WP_Term) {
                continue;
            }
            $add_overview_chip((string) $term_obj->name, eep_add_query_arg('event_mode', (string) $term_obj->slug, $events_base_url), false);
            if (count($overview_chips) >= 6) {
                break;
            }
        }
        foreach ((array) $type_term_objects as $term_obj) {
            if (!$term_obj instanceof WP_Term) {
                continue;
            }
            $add_overview_chip((string) $term_obj->name, eep_add_query_arg('event_type', (string) $term_obj->slug, $events_base_url), false);
            if (count($overview_chips) >= 6) {
                break;
            }
        }
        foreach ((array) $tag_term_objects as $term_obj) {
            if (!$term_obj instanceof WP_Term) {
                continue;
            }
            $add_overview_chip((string) $term_obj->name, eep_add_query_arg('event_tag', (string) $term_obj->slug, $events_base_url), false);
            if (count($overview_chips) >= 6) {
                break;
            }
        }

        $summary_cards = [];
        $detail_rows = [];
        if ($is_events_profile) {
            $summary_cards = array_filter([
                ['label' => $single_type_label, 'value' => $mode_value, 'compact' => true],
                ['label' => $single_price_range_label, 'value' => $price_value, 'compact' => true],
                ['label' => $single_classification_label, 'value' => $official ? (string) ($classification_labels['official_short'] ?? __('Official', 'nexdirectory')) : (string) ($classification_labels['unofficial_short'] ?? __('Unofficial', 'nexdirectory')), 'compact' => true],
            ], static function ($row) {
                return trim((string) ($row['value'] ?? '')) !== '';
            });
            if ($start_display !== '') {
                $detail_rows[] = ['label' => $single_start_label, 'value' => $start_display, 'compact' => false];
            }
            $same_day_event = $start_ts && (!$end_ts || wp_date('Y-m-d', $start_ts) === wp_date('Y-m-d', $end_ts));
            if ($end_display !== '' && !$same_day_event) {
                $detail_rows[] = ['label' => $single_end_label, 'value' => $end_display, 'compact' => false];
            }
            if ($timezone !== '') {
                $detail_rows[] = ['label' => $single_timezone_label, 'value' => $timezone, 'compact' => true];
            }
            if ($recurrence !== '' && $recurrence !== 'none') {
                $detail_rows[] = ['label' => $single_pattern_label, 'value' => ucfirst(str_replace('-', ' ', $recurrence)), 'compact' => false];
            }
            if ($type_names !== '') {
                $detail_rows[] = ['label' => $single_type_label, 'value' => $type_names, 'compact' => true];
            }
            if ($price !== '') {
                $detail_rows[] = ['label' => $single_price_label, 'value' => $price, 'compact' => true];
            }
            $capacity_int = is_numeric($capacity) ? (int) $capacity : 0;
            if ($capacity_int > 0) {
                $detail_rows[] = ['label' => $single_capacity_label, 'value' => (string) $capacity_int, 'compact' => true];
            }
            if ($tag_names !== '') {
                $detail_rows[] = ['label' => $single_tags_label, 'value' => $tag_names, 'compact' => false];
            }
            if ($recurrence_until_display !== '' && $recurrence !== '' && $recurrence !== 'none') {
                $detail_rows[] = ['label' => $single_until_label, 'value' => $recurrence_until_display, 'compact' => false];
            }
        } elseif ($is_job_profile) {
            $work_mode_display = $job_work_mode !== '' ? ucwords(str_replace('-', ' ', $job_work_mode)) : $mode_names;
            $summary_cards = array_filter([
                ['label' => __('Employment Type', 'nexdirectory'), 'value' => $job_type !== '' ? $job_type : $type_names, 'compact' => true],
                ['label' => __('Department', 'nexdirectory'), 'value' => $job_department !== '' ? $job_department : $type_names, 'compact' => true],
                ['label' => __('Location Type', 'nexdirectory'), 'value' => $work_mode_display, 'compact' => true],
            ], static function ($row) {
                return trim((string) ($row['value'] ?? '')) !== '';
            });
            if ($company_name !== '') {
                $detail_rows[] = ['label' => __('Company', 'nexdirectory'), 'value' => $company_name, 'compact' => true];
            }
            if ($job_experience_level !== '') {
                $detail_rows[] = ['label' => __('Experience Level', 'nexdirectory'), 'value' => $job_experience_level, 'compact' => true];
            }
            if ($job_reporting_to !== '') {
                $detail_rows[] = ['label' => __('Reporting Line', 'nexdirectory'), 'value' => $job_reporting_to, 'compact' => true];
            }
            if ($job_remote_policy !== '') {
                $detail_rows[] = ['label' => __('Remote Policy', 'nexdirectory'), 'value' => $job_remote_policy, 'compact' => true];
            }
            if ($job_salary !== '') {
                $detail_rows[] = ['label' => __('Salary', 'nexdirectory'), 'value' => $job_salary, 'compact' => true];
            }
            if ($job_company_size !== '') {
                $detail_rows[] = ['label' => __('Company Size', 'nexdirectory'), 'value' => $job_company_size, 'compact' => true];
            }
            if ($job_industry !== '') {
                $detail_rows[] = ['label' => __('Industry', 'nexdirectory'), 'value' => $job_industry, 'compact' => true];
            }
            if ($job_closing_display !== '') {
                $detail_rows[] = ['label' => __('Closing Date', 'nexdirectory'), 'value' => $job_closing_display, 'compact' => true];
            }
            if ($job_expected_start_display !== '') {
                $detail_rows[] = ['label' => __('Expected Start Date', 'nexdirectory'), 'value' => $job_expected_start_display, 'compact' => true];
            }
            $posted_ts = strtotime((string) get_post_field('post_date', $post_id));
            if ($posted_ts) {
                $detail_rows[] = ['label' => __('Posted On', 'nexdirectory'), 'value' => wp_date(get_option('date_format'), (int) $posted_ts), 'compact' => true];
            }
            if ($job_application_email !== '') {
                $detail_rows[] = ['label' => __('Application Email', 'nexdirectory'), 'value' => $job_application_email, 'url' => 'mailto:' . sanitize_email($job_application_email), 'compact' => false];
            }
            if ($job_apply_url !== '') {
                $detail_rows[] = ['label' => __('Application URL', 'nexdirectory'), 'value' => $job_apply_url, 'url' => $job_apply_url, 'metric' => 'click_website', 'compact' => false];
            }
            if ($job_company_website !== '' && untrailingslashit($job_company_website) !== untrailingslashit($job_apply_url)) {
                $detail_rows[] = ['label' => __('Company Website', 'nexdirectory'), 'value' => $job_company_website, 'url' => $job_company_website, 'metric' => 'click_website', 'compact' => false];
            }
            $skills_value = $job_skills !== '' ? $job_skills : $tag_names;
            if ($skills_value !== '') {
                $detail_rows[] = ['label' => $single_tags_label, 'value' => $skills_value, 'compact' => false];
            }
            $visa_label = $job_visa_sponsorship ? __('Yes', 'nexdirectory') : __('No', 'nexdirectory');
            $detail_rows[] = ['label' => __('Visa Sponsorship', 'nexdirectory'), 'value' => $visa_label, 'compact' => true];

            if ($job_responsibilities !== '') {
                $custom_fields_display[] = [
                    'label' => __('Responsibilities', 'nexdirectory'),
                    'render_type' => 'html',
                    'value' => $this->add_nofollow_to_external_links((string) wpautop(wp_kses_post($job_responsibilities))),
                ];
            }
            if ($job_requirements !== '') {
                $custom_fields_display[] = [
                    'label' => __('Requirements', 'nexdirectory'),
                    'render_type' => 'html',
                    'value' => $this->add_nofollow_to_external_links((string) wpautop(wp_kses_post($job_requirements))),
                ];
            }
            if ($job_benefits !== '') {
                $custom_fields_display[] = [
                    'label' => __('Benefits', 'nexdirectory'),
                    'render_type' => 'html',
                    'value' => $this->add_nofollow_to_external_links((string) wpautop(wp_kses_post($job_benefits))),
                ];
            }
            if ($job_interview_process !== '') {
                $custom_fields_display[] = [
                    'label' => __('Interview Process', 'nexdirectory'),
                    'render_type' => 'html',
                    'value' => $this->add_nofollow_to_external_links((string) wpautop(wp_kses_post($job_interview_process))),
                ];
            }
        } elseif ($is_directory_profile) {
            $directory_business_type_labels = [
                'independent-business' => __('Independent Business', 'nexdirectory'),
                'agency' => __('Agency', 'nexdirectory'),
                'private-owner' => __('Private Owner', 'nexdirectory'),
                'nonprofit' => __('Nonprofit / Community', 'nexdirectory'),
                'multi-location' => __('Multi-location Brand', 'nexdirectory'),
                'franchise' => __('Franchise', 'nexdirectory'),
            ];
            $directory_status_labels = [
                'active' => __('Active', 'nexdirectory'),
                'temporarily-closed' => __('Temporarily Closed', 'nexdirectory'),
                'permanently-closed' => __('Permanently Closed', 'nexdirectory'),
            ];
            $directory_business_type_display = (string) ($directory_business_type_labels[$directory_business_type] ?? '');
            $directory_status_display = (string) ($directory_status_labels[$directory_listing_status] ?? __('Active', 'nexdirectory'));

            $summary_cards = array_filter([
                ['label' => __('Business Type', 'nexdirectory'), 'value' => $directory_business_type_display, 'compact' => true],
                ['label' => __('Listing Status', 'nexdirectory'), 'value' => $directory_status_display, 'compact' => true],
                ['label' => __('Category', 'nexdirectory'), 'value' => $type_names, 'compact' => true],
            ], static function ($row) {
                return trim((string) ($row['value'] ?? '')) !== '';
            });
            $detail_rows[] = ['label' => __('Listing ID', 'nexdirectory'), 'value' => '#' . (string) $post_id, 'compact' => true];
            $posted_ts = strtotime((string) get_post_field('post_date', $post_id));
            if ($posted_ts) {
                $detail_rows[] = ['label' => __('Listed On', 'nexdirectory'), 'value' => wp_date(get_option('date_format'), (int) $posted_ts), 'compact' => true];
            }
            if ($company_name !== '') {
                $detail_rows[] = ['label' => __('Business Name', 'nexdirectory'), 'value' => $company_name, 'compact' => true];
            }
            if ($directory_contact_person !== '') {
                $detail_rows[] = ['label' => __('Contact Person', 'nexdirectory'), 'value' => $directory_contact_person, 'compact' => true];
            }
            $updated_ts = strtotime((string) get_post_field('post_modified', $post_id));
            if ($updated_ts) {
                $detail_rows[] = ['label' => __('Last Updated', 'nexdirectory'), 'value' => wp_date(get_option('date_format'), (int) $updated_ts), 'compact' => true];
            }
            if ($directory_service_area !== '') {
                $detail_rows[] = ['label' => __('Service Area', 'nexdirectory'), 'value' => $directory_service_area, 'compact' => true];
            }
            if ($directory_operating_hours !== '') {
                $detail_rows[] = ['label' => __('Operating Hours', 'nexdirectory'), 'value' => $directory_operating_hours, 'compact' => true];
            }
            if ($directory_response_time !== '') {
                $detail_rows[] = ['label' => __('Response Time', 'nexdirectory'), 'value' => $directory_response_time, 'compact' => true];
            }
            if ($directory_established_year > 0) {
                $detail_rows[] = ['label' => __('Established', 'nexdirectory'), 'value' => (string) $directory_established_year, 'compact' => true];
            }
            if ($directory_pricing_summary !== '') {
                $detail_rows[] = ['label' => __('Pricing Summary', 'nexdirectory'), 'value' => $directory_pricing_summary, 'compact' => false];
            }
            if ($tag_names !== '') {
                $detail_rows[] = ['label' => $single_tags_label, 'value' => $tag_names, 'compact' => false];
            }
            if ($directory_appointment_url !== '') {
                $detail_rows[] = ['label' => __('Book Appointment', 'nexdirectory'), 'value' => $directory_appointment_url, 'url' => $directory_appointment_url, 'metric' => 'click_website', 'compact' => false];
            }
            if ($directory_whatsapp_url !== '') {
                $detail_rows[] = ['label' => __('WhatsApp', 'nexdirectory'), 'value' => $directory_whatsapp_url, 'url' => $directory_whatsapp_url, 'metric' => 'click_whatsapp', 'compact' => false];
            }

            if ($directory_services !== '') {
                $custom_fields_display[] = [
                    'label' => __('Services Offered', 'nexdirectory'),
                    'render_type' => 'html',
                    'value' => $this->add_nofollow_to_external_links((string) wpautop(wp_kses_post($directory_services))),
                ];
            }
            if ($directory_highlights !== '') {
                $custom_fields_display[] = [
                    'label' => __('Highlights', 'nexdirectory'),
                    'render_type' => 'html',
                    'value' => $this->add_nofollow_to_external_links((string) wpautop(wp_kses_post($directory_highlights))),
                ];
            }
            if ($directory_certifications !== '') {
                $custom_fields_display[] = [
                    'label' => __('Certifications / Credentials', 'nexdirectory'),
                    'render_type' => 'html',
                    'value' => $this->add_nofollow_to_external_links((string) wpautop(wp_kses_post($directory_certifications))),
                ];
            }
        } elseif ($is_marketplace_profile) {
            $market_price_type_labels = [
                'fixed' => __('Fixed Price', 'nexdirectory'),
                'negotiable' => __('Negotiable', 'nexdirectory'),
                'auction' => __('Auction', 'nexdirectory'),
                'free' => __('Free', 'nexdirectory'),
            ];
            $market_status_labels = [
                'active' => __('Available', 'nexdirectory'),
                'reserved' => __('Reserved', 'nexdirectory'),
                'sold' => __('Sold', 'nexdirectory'),
                'expired' => __('Expired', 'nexdirectory'),
            ];
            $market_seller_labels = [
                'private' => __('Private Seller', 'nexdirectory'),
                'business' => __('Business Seller', 'nexdirectory'),
            ];
            $market_delivery_label_map = [
                'pickup' => __('Pickup', 'nexdirectory'),
                'shipping' => __('Shipping', 'nexdirectory'),
                'local-delivery' => __('Local Delivery', 'nexdirectory'),
                'digital' => __('Digital Delivery', 'nexdirectory'),
            ];
            $market_delivery_display = [];
            foreach ((array) $market_delivery_methods as $delivery_key) {
                if (isset($market_delivery_label_map[$delivery_key])) {
                    $market_delivery_display[] = (string) $market_delivery_label_map[$delivery_key];
                }
            }
            $market_price_type_display = (string) ($market_price_type_labels[$market_price_type] ?? '');
            $market_seller_type_display = (string) ($market_seller_labels[$market_seller_type] ?? '');
            $market_status_display = (string) ($market_status_labels[$market_listing_status] ?? __('Available', 'nexdirectory'));
            if ($market_expires_ts && $market_expires_ts < current_time('timestamp')) {
                $market_status_display = __('Expired', 'nexdirectory');
            }
            $summary_cards = array_filter([
                ['label' => __('Condition', 'nexdirectory'), 'value' => $market_condition, 'compact' => true],
                ['label' => __('Price Type', 'nexdirectory'), 'value' => $market_price_type_display, 'compact' => true],
                ['label' => __('Seller Type', 'nexdirectory'), 'value' => $market_seller_type_display, 'compact' => true],
                ['label' => __('Availability Status', 'nexdirectory'), 'value' => $market_status_display, 'compact' => true],
            ], static function ($row) {
                return trim((string) ($row['value'] ?? '')) !== '';
            });
            $posted_ts = strtotime((string) get_post_field('post_date', $post_id));
            $updated_ts = strtotime((string) get_post_field('post_modified', $post_id));
            $listing_views = 0;
            if ($this->analytics_table_exists()) {
                global $wpdb;
                $analytics_table = $this->get_analytics_table_name();
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                $listing_views = (int) $wpdb->get_var($wpdb->prepare(
                    "SELECT COALESCE(SUM(value), 0) FROM {$analytics_table} WHERE event_id = %d AND metric = %s",
                    $post_id,
                    'listing_view'
                ));
            }
            $detail_rows[] = ['label' => __('Listing ID', 'nexdirectory'), 'value' => '#' . (string) $post_id, 'compact' => true];
            if ($posted_ts) {
                $detail_rows[] = ['label' => __('Posted On', 'nexdirectory'), 'value' => wp_date(get_option('date_format'), (int) $posted_ts), 'compact' => true];
            }
            if ($updated_ts) {
                $detail_rows[] = ['label' => __('Last Updated', 'nexdirectory'), 'value' => wp_date(get_option('date_format'), (int) $updated_ts), 'compact' => true];
            }
            if ($market_expires_display !== '') {
                $detail_rows[] = ['label' => __('Expires On', 'nexdirectory'), 'value' => $market_expires_display, 'compact' => true];
            }
            $detail_rows[] = ['label' => __('Views', 'nexdirectory'), 'value' => (string) number_format_i18n($listing_views), 'compact' => true];
            $detail_rows[] = ['label' => __('Accepts Offers', 'nexdirectory'), 'value' => $market_accept_offers ? __('Yes', 'nexdirectory') : __('No', 'nexdirectory'), 'compact' => true];
            if ($price !== '') {
                $detail_rows[] = ['label' => __('Asking Price', 'nexdirectory'), 'value' => $price, 'compact' => true];
            }
            if ($market_quantity > 0) {
                $detail_rows[] = ['label' => __('Quantity', 'nexdirectory'), 'value' => (string) $market_quantity, 'compact' => true];
            }
            if ($market_brand !== '') {
                $detail_rows[] = ['label' => __('Brand', 'nexdirectory'), 'value' => $market_brand, 'compact' => true];
            }
            if ($market_model !== '') {
                $detail_rows[] = ['label' => __('Model', 'nexdirectory'), 'value' => $market_model, 'compact' => true];
            }
            if ($market_year > 0) {
                $detail_rows[] = ['label' => __('Year', 'nexdirectory'), 'value' => (string) $market_year, 'compact' => true];
            }
            if ($market_sku !== '') {
                $detail_rows[] = ['label' => __('SKU', 'nexdirectory'), 'value' => $market_sku, 'compact' => true];
            }
            if ($type_names !== '') {
                $detail_rows[] = ['label' => __('Category', 'nexdirectory'), 'value' => $type_names, 'compact' => true];
            }
            if (!empty($market_delivery_display)) {
                $detail_rows[] = ['label' => __('Delivery', 'nexdirectory'), 'value' => implode(', ', $market_delivery_display), 'compact' => false];
            }
            if ($market_payment_methods !== '') {
                $detail_rows[] = ['label' => __('Payment Methods', 'nexdirectory'), 'value' => $market_payment_methods, 'compact' => false];
            }
            $detail_rows[] = ['label' => __('Returns Accepted', 'nexdirectory'), 'value' => $market_returns_accepted ? __('Yes', 'nexdirectory') : __('No', 'nexdirectory'), 'compact' => true];
            if ($tag_names !== '') {
                $detail_rows[] = ['label' => $single_tags_label, 'value' => $tag_names, 'compact' => false];
            }
            if ($market_condition_notes !== '') {
                $custom_fields_display[] = [
                    'label' => __('Condition Notes', 'nexdirectory'),
                    'render_type' => 'html',
                    'value' => $this->add_nofollow_to_external_links((string) wpautop(wp_kses_post($market_condition_notes))),
                ];
            }
            if ($market_shipping_notes !== '') {
                $custom_fields_display[] = [
                    'label' => __('Delivery / Collection Notes', 'nexdirectory'),
                    'render_type' => 'html',
                    'value' => $this->add_nofollow_to_external_links((string) wpautop(wp_kses_post($market_shipping_notes))),
                ];
            }
        } else {
            $summary_cards = array_filter([
                ['label' => $single_type_label, 'value' => $type_names, 'compact' => true],
                ['label' => $single_price_range_label, 'value' => $price_value, 'compact' => true],
            ], static function ($row) {
                return trim((string) ($row['value'] ?? '')) !== '';
            });
            if ($tag_names !== '') {
                $detail_rows[] = ['label' => $single_tags_label, 'value' => $tag_names, 'compact' => false];
            }
        }

        $summary_cards = array_map(static function ($row) {
            $row['value'] = trim((string) preg_replace('/\s+/u', ' ', (string) ($row['value'] ?? '')));
            $row['compact'] = !empty($row['compact']);
            return $row;
        }, $summary_cards);

        foreach ($this->get_custom_taxonomy_definitions_for_overview() as $taxonomy_row) {
            $taxonomy_slug = sanitize_key((string) ($taxonomy_row['slug'] ?? ''));
            if ($taxonomy_slug === '' || !taxonomy_exists($taxonomy_slug)) {
                continue;
            }
            if ($is_job_profile || $is_marketplace_profile || $is_directory_profile) {
                $taxonomy_label_lc = function_exists('mb_strtolower')
                    ? mb_strtolower((string) ($taxonomy_row['label'] ?? ''), 'UTF-8')
                    : strtolower((string) ($taxonomy_row['label'] ?? ''));
                if (
                    strpos($taxonomy_label_lc, 'event type') !== false
                    || strpos($taxonomy_label_lc, 'price range') !== false
                    || strpos($taxonomy_label_lc, 'classification') !== false
                    || strpos($taxonomy_label_lc, 'event start') !== false
                    || strpos($taxonomy_label_lc, 'event end') !== false
                    || strpos($taxonomy_label_lc, 'timezone') !== false
                    || strpos($taxonomy_label_lc, 'capacity') !== false
                    || strpos($taxonomy_label_lc, 'primary partner') !== false
                    || strpos($taxonomy_label_lc, 'owner notes') !== false
                ) {
                    continue;
                }
            }
            $term_names = wp_get_post_terms($post_id, $taxonomy_slug, ['fields' => 'names']);
            if (is_wp_error($term_names) || empty($term_names)) {
                continue;
            }
            $term_display = implode(', ', array_map('eep_sanitize_text', (array) $term_names));
            if (trim($term_display) === '') {
                continue;
            }
            $detail_rows[] = [
                'label' => sanitize_text_field((string) ($taxonomy_row['label'] ?? $taxonomy_slug)),
                'value' => $term_display,
                'compact' => false,
            ];
        }
        if (trim((string) $contact_phone) !== '' && !$is_marketplace_profile) {
            $detail_rows[] = ['label' => $single_phone_label, 'value' => $contact_phone, 'compact' => true];
        }
        if ($contact_email !== '' && !$is_marketplace_profile && !($is_job_profile && $job_application_email !== '' && strtolower((string) $contact_email) === strtolower((string) $job_application_email))) {
            $detail_rows[] = ['label' => $single_contact_email_label, 'value' => $contact_email, 'url' => 'mailto:' . sanitize_email($contact_email), 'compact' => false];
        }
        if ($external_url !== '' && !$is_marketplace_profile && !($is_job_profile && (
            ($job_apply_url !== '' && untrailingslashit($external_url) === untrailingslashit($job_apply_url))
            || ($job_company_website !== '' && untrailingslashit($external_url) === untrailingslashit($job_company_website))
        ))) {
            $detail_rows[] = [
                'label' => $single_website_label,
                'value' => $external_url,
                'url' => $external_url,
                'metric' => $external_click_metric,
                'compact' => false,
            ];
        }
        if ($is_events_profile && $stream_url !== '') {
            $detail_rows[] = ['label' => $single_video_label, 'value' => $stream_url, 'url' => $stream_url, 'compact' => false];
        }
        $detail_rows = array_values(array_filter($detail_rows, static function ($row) {
            return trim((string) ($row['value'] ?? '')) !== '';
        }));
        $detail_rows = array_map(static function ($row) {
            $row['value'] = trim((string) preg_replace('/\s+/u', ' ', (string) ($row['value'] ?? '')));
            $row['compact'] = !empty($row['compact']);
            return $row;
        }, $detail_rows);
        $overview_has_content = (
            trim((string) eep_strip_all_tags((string) $content_html)) !== ''
            || !empty($summary_cards)
            || !empty($overview_chips)
            || !empty($detail_rows)
        );

        $all_images = [];
        if ($cover_image !== '') {
            $all_images[] = $cover_image;
        }
        foreach ($gallery_urls as $gallery_url) {
            $all_images[] = esc_url((string) $gallery_url);
        }
        $all_images = array_values(array_unique(array_filter($all_images)));
        $video_cards = $this->build_event_video_cards($video_urls, $cover_image !== '' ? $cover_image : ($all_images[0] ?? ''));
        $supporting_document_cards = $this->build_event_supporting_document_cards($supporting_doc_urls);

        $sessions_rows = [];
        if ($is_events_profile) {
            $raw_sessions = json_decode((string) get_post_meta($post_id, 'eep_sessions', true), true);
            if (is_array($raw_sessions)) {
                foreach ($raw_sessions as $index => $session) {
                    if (!is_array($session)) {
                        continue;
                    }
                    $session_start_raw = (string) ($session['start'] ?? '');
                    $session_end_raw = (string) ($session['end'] ?? '');
                    $session_start = $session_start_raw !== '' ? strtotime($session_start_raw) : false;
                    $session_end = $session_end_raw !== '' ? strtotime($session_end_raw) : false;
                    $time_bits = [];
                    if ($session_start) {
                        if ($this->event_datetime_has_time($session_start_raw)) {
                            $time_bits[] = wp_date(get_option('time_format'), (int) $session_start);
                        } else {
                            $time_bits[] = wp_date(get_option('date_format'), (int) $session_start);
                        }
                    }
                    if ($session_end) {
                        if ($this->event_datetime_has_time($session_end_raw)) {
                            $time_bits[] = wp_date(get_option('time_format'), (int) $session_end);
                        } else {
                            $time_bits[] = wp_date(get_option('date_format'), (int) $session_end);
                        }
                    }
                    if ($session_start && $session_end && wp_date('Y-m-d', (int) $session_start) !== wp_date('Y-m-d', (int) $session_end)) {
                        $time_bits = [
                            $this->format_event_datetime_for_display($session_start_raw),
                            $this->format_event_datetime_for_display($session_end_raw),
                        ];
                    }

                    $day_label = sanitize_text_field((string) ($session['day'] ?? ''));
                    $day_key = '';
                    if ($session_start) {
                        $day_key = wp_date('Y-m-d', (int) $session_start);
                        if ($day_label === '') {
                            $day_label = wp_date(get_option('date_format'), (int) $session_start);
                        }
                    } elseif ($day_label === '' && $start_ts) {
                        $day_key = wp_date('Y-m-d', (int) $start_ts);
                        $day_label = wp_date(get_option('date_format'), (int) $start_ts);
                    } elseif ($day_label !== '' && preg_match('/^\d{4}\-\d{2}\-\d{2}$/', $day_label)) {
                        $day_key = $day_label;
                        $day_label = wp_date(get_option('date_format'), strtotime($day_label . ' 00:00:00'));
                    }

                    $attachments_raw = $session['attachments'] ?? [];
                    if (is_string($attachments_raw)) {
                        $attachments_raw = preg_split('/[\r\n,]+/', $attachments_raw) ?: [];
                    }
                    $attachments = [];
                    foreach ((array) $attachments_raw as $attachment_item) {
                        $attachment_value = trim((string) $attachment_item);
                        if ($attachment_value === '') {
                            continue;
                        }
                        if (is_numeric($attachment_value)) {
                            $attachment_id = absint((int) $attachment_value);
                            if ($attachment_id > 0) {
                                $resolved_url = wp_get_attachment_url($attachment_id);
                                if (is_string($resolved_url) && $resolved_url !== '') {
                                    $attachment_value = $resolved_url;
                                } else {
                                    continue;
                                }
                            }
                        }
                        if (preg_match('/^[A-Za-z0-9_-]{11}$/', $attachment_value)) {
                            $attachment_value = 'https://www.youtube.com/watch?v=' . $attachment_value;
                        } elseif (preg_match('/^(?:youtube\.com|youtu\.be|vimeo\.com)\//i', $attachment_value)) {
                            $attachment_value = 'https://' . $attachment_value;
                        } elseif (preg_match('/^[a-z0-9.-]+\.[a-z]{2,}(?:\/|$)/i', $attachment_value) && !preg_match('/^[a-z]+:\/\//i', $attachment_value)) {
                            $attachment_value = 'https://' . $attachment_value;
                        }
                        $attachment_url = esc_url_raw($attachment_value);
                        if ($attachment_url !== '') {
                            $attachments[] = $attachment_url;
                        }
                    }
                    $attachments = array_values(array_unique($attachments));

                    $sessions_rows[] = [
                        'title' => !empty($session['title']) ? sanitize_text_field((string) $session['title']) : sprintf(__('Session %d', 'nexdirectory'), $index + 1),
                        'time' => implode(' - ', $time_bits),
                        'location' => sanitize_text_field((string) ($session['location'] ?? '')),
                        'speaker' => sanitize_text_field((string) ($session['speaker'] ?? '')),
                        'day_key' => $day_key,
                        'day_label' => $day_label,
                        'start_raw' => $session_start_raw,
                        'end_raw' => $session_end_raw,
                        'notes' => eep_sanitize_textarea((string) ($session['notes'] ?? '')),
                        'attachments' => $attachments,
                        'session_key' => sanitize_key((string) ($session['session_key'] ?? '')),
                    ];
                }
            }
        } elseif ($is_job_profile) {
            $posted_ts = strtotime((string) get_post_field('post_date', $post_id));
            if ($posted_ts) {
                $sessions_rows[] = [
                    'title' => __('Posted On', 'nexdirectory'),
                    'time' => wp_date(get_option('date_format'), (int) $posted_ts),
                    'location' => '',
                    'speaker' => '',
                ];
            }
            if ($job_closing_display !== '') {
                $sessions_rows[] = [
                    'title' => __('Application Deadline', 'nexdirectory'),
                    'time' => $job_closing_display,
                    'location' => '',
                    'speaker' => '',
                ];
            }
            if ($job_expected_start_display !== '') {
                $sessions_rows[] = [
                    'title' => __('Expected Start Date', 'nexdirectory'),
                    'time' => $job_expected_start_display,
                    'location' => '',
                    'speaker' => '',
                ];
            }
            if ($job_interview_process !== '') {
                $sessions_rows[] = [
                    'title' => __('Interview Process', 'nexdirectory'),
                    'time' => eep_sanitize_text($job_interview_process),
                    'location' => '',
                    'speaker' => '',
                ];
            }
        }
        $key_speakers_rows = $is_events_profile
            ? $this->parse_key_speakers((string) get_post_meta($post_id, 'eep_key_speakers', true))
            : [];

        $start = strtotime((string) get_post_meta($post_id, 'eep_start', true));
        $end = strtotime((string) get_post_meta($post_id, 'eep_end', true));
        if (!$end || $end <= $start) {
            if ($start && !$start_has_time) {
                $end = $start + DAY_IN_SECONDS;
            } else {
                $end = $start ? ($start + HOUR_IN_SECONDS) : time() + HOUR_IN_SECONDS;
            }
        }
        $calendar_url = eep_add_query_arg('eep_ics', $post_id, home_url('/'));
        $google_url = eep_add_query_arg([
            'action' => 'TEMPLATE',
            'text' => get_the_title($post_id),
            'dates' => gmdate('Ymd\THis\Z', $start ?: time()) . '/' . gmdate('Ymd\THis\Z', $end),
            'details' => eep_strip_all_tags(get_post_field('post_excerpt', $post_id) ?: get_post_field('post_content', $post_id)),
            'location' => $address !== '' ? $address : $city,
        ], 'https://calendar.google.com/calendar/render');

        $organizer_id = (int) get_post_field('post_author', $post_id);
        $verified = get_user_meta($organizer_id, 'eep_verified_organizer', true) === '1';
        $organizer_name = get_the_author_meta('display_name', $organizer_id);
        $organizer_email = get_the_author_meta('user_email', $organizer_id);
        $organizer_events_url = eep_add_query_arg('organizer', $organizer_id, $events_base_url);
        $organizer_registered_raw = (string) get_the_author_meta('user_registered', $organizer_id);
        $organizer_member_since = '';
        if ($organizer_registered_raw !== '') {
            $organizer_registered_ts = strtotime($organizer_registered_raw);
            if ($organizer_registered_ts) {
                $organizer_member_since = wp_date(get_option('date_format'), (int) $organizer_registered_ts);
            }
        }
        $organizer_listing_count = $organizer_id > 0 ? (int) count_user_posts($organizer_id, self::POST_TYPE, true) : 0;

        $viewer_logged_in = is_user_logged_in();
        $rsvp = $this->get_event_attendees_by_status($post_id, 'yes');
        $waitlist = $this->get_event_attendees_by_status($post_id, 'waitlist');
        $is_joined = $viewer_logged_in && (in_array(get_current_user_id(), $rsvp, true) || in_array(get_current_user_id(), $waitlist, true));
        $post_status = (string) get_post_status($post_id);
        $now_ts = current_time('timestamp');
        $countdown_target = ($start_ts && $start_ts > $now_ts) ? (int) $start_ts : 0;
        if ($is_job_profile && $job_closing_ts && $job_closing_ts > $now_ts) {
            $countdown_target = (int) $job_closing_ts;
        }
        $show_rsvp_actions = $is_events_profile || $is_marketplace_profile;
        $show_calendar_actions = $is_events_profile;
        $show_countdown = ($is_events_profile || $is_job_profile) && $countdown_target > 0;
        $show_timeline = ($is_events_profile || $is_job_profile) && !empty($sessions_rows);
        $show_key_speakers = $is_events_profile && !empty($key_speakers_rows);
        $platform_mode = sanitize_key((string) get_option('eep_platform_mode', 'user_generated'));
        $single_org_profile = $platform_mode === 'single_organisation' || $resolved_profile_key === 'events_single_organisation';
        $single_org_mode = $is_events_profile && $single_org_profile;
        $show_organizer_card = !$single_org_mode;

        $ticket_sales_enabled = $this->is_ticket_sales_enabled_for_post($post_id);
        $ticket_price_raw = trim((string) get_post_meta($post_id, 'eep_ticket_price', true));
        $ticket_price_amount = 0.0;
        if ($ticket_price_raw !== '') {
            $normalized_ticket_price = str_replace(',', '.', $ticket_price_raw);
            if (is_numeric($normalized_ticket_price)) {
                $ticket_price_amount = max(0.0, (float) $normalized_ticket_price);
            } elseif (preg_match('/\d+(?:\.\d{1,2})?/', $normalized_ticket_price, $ticket_price_match)) {
                $ticket_price_amount = max(0.0, (float) ($ticket_price_match[0] ?? 0));
            }
        }
        $ticket_is_paid = $ticket_price_amount > 0;
        $ticket_button_label = sanitize_text_field((string) get_post_meta($post_id, 'eep_ticket_button_label', true));
        if ($ticket_button_label === '') {
            $ticket_button_label = $ticket_is_paid ? __('Buy Now', 'nexdirectory') : __('Reserve Free Ticket', 'nexdirectory');
        }
        $ticket_currency = $this->get_ticket_currency();
        $ticket_price_display = $ticket_is_paid
            ? sprintf('%1$s %2$s', $ticket_currency, number_format($ticket_price_amount, 2))
            : __('Free', 'nexdirectory');
        $ticket_gateway_stripe_enabled = $this->is_ticket_gateway_enabled('stripe');
        $ticket_gateway_paypal_enabled = $this->is_ticket_gateway_enabled('paypal');
        $ticket_test_free_enabled = $this->ticket_test_free_mode_enabled();
        $ticket_gateway_default = $ticket_gateway_stripe_enabled
            ? 'stripe'
            : ($ticket_gateway_paypal_enabled ? 'paypal' : '');
        if ($ticket_gateway_default === '' && $ticket_test_free_enabled) {
            $ticket_gateway_default = 'test_free';
        }
        $has_ticket_gateway_available = !$ticket_is_paid || $ticket_test_free_enabled || $ticket_gateway_stripe_enabled || $ticket_gateway_paypal_enabled;
        $ticket_checkout_enabled = $this->is_ticket_checkout_enabled_for_post($post_id);
        $show_ticket_cta = $is_events_profile && $ticket_checkout_enabled && $ticket_sales_enabled;
        if ($single_org_mode) {
            $show_rsvp_actions = false;
        }
        $capacity_int = is_numeric($capacity) ? (int) $capacity : 0;
        if ($post_status !== 'publish') {
            $status_text = ucfirst($post_status);
        } elseif ($is_marketplace_profile) {
            $market_status_map = [
                'active' => __('Available', 'nexdirectory'),
                'reserved' => __('Reserved', 'nexdirectory'),
                'sold' => __('Sold', 'nexdirectory'),
                'expired' => __('Expired', 'nexdirectory'),
            ];
            if ($market_expires_ts && $market_expires_ts < $now_ts) {
                $status_text = __('Expired', 'nexdirectory');
            } else {
                $status_text = (string) ($market_status_map[$market_listing_status] ?? __('Available', 'nexdirectory'));
            }
        } elseif ($is_job_profile && $job_closing_ts && $job_closing_ts < $now_ts) {
            $status_text = __('Applications Closed', 'nexdirectory');
        } elseif ($is_events_profile && $end_ts && $end_ts < $now_ts) {
            $status_text = __('Closed', 'nexdirectory');
        } elseif ($is_events_profile && $capacity_int > 0 && count($rsvp) >= $capacity_int) {
            $status_text = $is_events_profile ? __('Waitlist Only', 'nexdirectory') : __('At Capacity', 'nexdirectory');
        } elseif ($is_job_profile) {
            $status_text = __('Accepting Applications', 'nexdirectory');
        } elseif ($is_directory_profile) {
            $directory_status_map = [
                'active' => __('Active', 'nexdirectory'),
                'temporarily-closed' => __('Temporarily Closed', 'nexdirectory'),
                'permanently-closed' => __('Permanently Closed', 'nexdirectory'),
            ];
            $status_text = (string) ($directory_status_map[$directory_listing_status] ?? __('Active', 'nexdirectory'));
        } else {
            $status_text = $is_events_profile ? __('RSVP Open', 'nexdirectory') : __('Open', 'nexdirectory');
        }
        $owner_id = (int) get_post_field('post_author', $post_id);
        $current_user_id = is_user_logged_in() ? (int) get_current_user_id() : 0;
        $owner_event_messages_enabled = $owner_id > 0 ? (bool) $this->user_allows_event_messages($owner_id) : true;
        $is_owner = $current_user_id > 0 && $current_user_id === $owner_id;
        $can_edit_event = $current_user_id > 0 && $this->user_can_edit_event($post_id, $current_user_id);
        $can_duplicate_locked_demo = !$can_edit_event
            && $current_user_id > 0
            && method_exists($this, 'user_can_duplicate_event')
            && (bool) $this->user_can_duplicate_event($post_id, $current_user_id);
        $claim_enabled = (string) get_option('eep_enable_claim_listings', '0') === '1';
        $claimable = $claim_enabled && $this->is_event_claimable($post_id) && !$is_owner;
        $claim_status = '';
        if ($claimable && is_user_logged_in()) {
            $claim_status = $this->get_user_claim_status_for_event($post_id, get_current_user_id());
        }
        $claim_page_url = $this->get_platform_page_url(
            'eep_claim_listing_page_url',
            $this->get_profile_route_slugs('claim'),
            $this->get_profile_route_default_url('claim')
        );
        $claim_url = $claimable ? eep_add_query_arg('event_id', $post_id, $claim_page_url) : '';
        $import_source = sanitize_key((string) get_post_meta($post_id, 'eep_import_source', true));
        $is_plugin_generated_event = (string) get_post_meta($post_id, 'eep_generated_by_plugin', true) === '1' || $import_source === 'chrome_extension';
        $show_autogenerated_disclaimer = $is_plugin_generated_event && (string) get_option('eep_enable_autogenerated_disclaimer', '1') === '1';
        $autogenerated_disclaimer_text = eep_sanitize_textarea((string) get_option('eep_autogenerated_disclaimer_text', ''));
        if ($show_autogenerated_disclaimer && $autogenerated_disclaimer_text === '') {
            $site_name = eep_strip_all_tags((string) $this->get_white_label_brand_name());
            if ($site_name === '') {
                $site_name = __('This website', 'nexdirectory');
            }
            if ($is_events_profile) {
                $autogenerated_disclaimer_text = sprintf(
                    __('This listing was auto-generated from a third-party source and event details may change without notice. Please verify date, time, location, and registration on the original host website. %s is not affiliated with the event host and cannot guarantee the accuracy of imported information. If you own this event, please claim the listing or contact us to request updates or removal.', 'nexdirectory'),
                    $site_name
                );
            } elseif ($is_job_profile) {
                $autogenerated_disclaimer_text = sprintf(
                    __('This job listing was auto-generated from a third-party source and details may change without notice. Please verify role requirements, location, and application information on the original employer website. %s is not affiliated with the employer and cannot guarantee the accuracy of imported information. If you own this listing, please claim it or contact us to request updates or removal.', 'nexdirectory'),
                    $site_name
                );
            } else {
                $autogenerated_disclaimer_text = sprintf(
                    __('This %1$s was auto-generated from a third-party source and details may change without notice. Please verify information on the original source website. %2$s is not affiliated with the original source and cannot guarantee the accuracy of imported information. If you own this listing, please claim it or contact us to request updates or removal.', 'nexdirectory'),
                    $profile_singular_lower,
                    $site_name
                );
            }
        }
        $autogenerated_source_url = $is_plugin_generated_event ? esc_url_raw((string) get_post_meta($post_id, 'eep_import_source_url', true)) : '';
        $edit_event_url = '';
        $edit_event_is_duplicate = false;
        if ($can_edit_event) {
            $edit_target_id = $post_id;
            $submit_event_url = $this->get_platform_page_url(
                'eep_page_submit_event_url',
                $this->get_profile_route_slugs('submit'),
                $this->get_profile_route_default_url('submit')
            );
            if ($submit_event_url !== '') {
                $edit_event_url = eep_add_query_arg([
                    'eep_tab' => 'submit',
                    'edit_event_id' => $edit_target_id,
                ], $submit_event_url) . '#submit';
            } else {
                $edit_event_url = '';
            }
        } elseif ($can_duplicate_locked_demo) {
            $edit_event_url = wp_nonce_url(
                eep_add_query_arg([
                    'action' => 'eep_duplicate_event',
                    'post' => $post_id,
                ], admin_url('admin.php')),
                'eep_duplicate_event_' . $post_id
            );
            $edit_event_is_duplicate = true;
        }

        $hero_style_attr = $cover_image !== '' ? ' style="background-image:url(' . esc_url($cover_image) . ')"' : '';
        $hero_kicker_text = $this->get_event_hero_kicker_text();
        $hero_date_label = '';
        $show_hero_date_card = !$is_marketplace_profile;
        if ($is_marketplace_profile) {
            $hero_date_label = '';
        } elseif ($is_job_profile && $job_closing_ts) {
            $hero_date_label = sprintf(
                /* translators: %s: date */
                __('Closes %s', 'nexdirectory'),
                wp_date('M j, Y', (int) $job_closing_ts)
            );
        } elseif ($start_ts && $end_ts && wp_date('Y-m-d', (int) $end_ts) !== wp_date('Y-m-d', (int) $start_ts)) {
            $hero_date_label = (string) $this->format_event_date($post_id);
        } elseif ($start_ts) {
            $hero_date_label = wp_date('M j, Y', (int) $start_ts);
        } else {
            $posted_ts = strtotime((string) get_post_field('post_date', $post_id));
            $hero_date_label = $posted_ts ? wp_date('M j, Y', (int) $posted_ts) : __('Date TBD', 'nexdirectory');
        }
        $hero_location_label = $location_line !== '' ? $location_line : '';
        $job_application_deadline = $is_job_profile ? $job_closing_display : '';
        $local_time_start_iso = ($start && $start_has_time) ? gmdate('c', (int) $start) : '';
        $local_time_end_iso = ($end && $end_has_time) ? gmdate('c', (int) $end) : '';
        $login_url = $this->get_login_signup_url(get_permalink($post_id));
        $signup_url = $this->get_auth_screen_url('signup', get_permalink($post_id));
        $whos_attending_url = '';
        if ($is_events_profile) {
            $whos_attending_base_url = $this->get_platform_page_url(
                'eep_page_whos_attending_url',
                $this->get_profile_route_slugs('attendees', $resolved_profile_key),
                $this->get_profile_route_default_url('attendees', $resolved_profile_key)
            );
            if ($whos_attending_base_url !== '') {
                $whos_attending_url = eep_add_query_arg([
                    'event_id' => $post_id,
                    'status' => 'yes',
                ], $whos_attending_base_url);
            }
        }
        $event_webapp_settings = $is_events_profile ? $this->get_event_webapp_settings($post_id) : [];
        $event_webapp_enabled = $is_events_profile && !empty($event_webapp_settings['enabled']);
        $event_webapp_url = $event_webapp_enabled ? (string) $this->get_event_webapp_page_url($post_id) : '';
        $single_open_event_app_cta = sanitize_text_field((string) ($ui_labels['single_open_event_app_cta'] ?? __('Download App', 'nexdirectory')));
        $primary_action_url = '';
        $primary_action_label = '';
        $primary_action_metric = '';
        if ($is_directory_profile && $directory_appointment_url !== '') {
            $primary_action_url = $directory_appointment_url;
            $primary_action_label = $single_directory_book_cta;
            $primary_action_metric = 'click_website';
        } elseif ($is_directory_profile && $directory_whatsapp_url !== '') {
            $primary_action_url = $directory_whatsapp_url;
            $primary_action_label = $single_directory_whatsapp_cta;
            $primary_action_metric = 'click_whatsapp';
        } elseif ($is_job_profile && $job_apply_url !== '') {
            $primary_action_url = $job_apply_url;
            $primary_action_label = $single_visit_site_cta;
            $primary_action_metric = 'click_website';
        } elseif ($is_job_profile && $job_application_email !== '') {
            $primary_action_url = 'mailto:' . sanitize_email($job_application_email);
            $primary_action_label = $single_apply_email_cta;
            $primary_action_metric = 'click_contact_email';
        } elseif (!$is_events_profile && $external_url !== '') {
            $primary_action_url = $external_url;
            $primary_action_label = $single_visit_site_cta;
            $primary_action_metric = $external_click_metric;
        }
        $hero_layout = sanitize_key((string) get_option('eep_event_hero_layout', 'cinematic'));
        if (!in_array($hero_layout, ['cinematic', 'compact', 'split', 'minimal', 'immersive', 'spotlight'], true)) {
            $hero_layout = 'cinematic';
        }
        $page_layout = sanitize_key((string) get_option('eep_event_page_layout', 'modern'));
        if (in_array($page_layout, ['balanced', 'compact-aside', 'cards-heavy'], true)) {
            $page_layout = 'fifty-fifty';
        }
        if (!in_array($page_layout, ['modern', 'single-column', 'editorial', 'fifty-fifty', 'left-sidebar', 'zig-zag', 'full3', 'zen'], true)) {
            $page_layout = 'modern';
        }
        $event_gallery_type = method_exists($this, 'sanitize_event_gallery_type_option')
            ? $this->sanitize_event_gallery_type_option((string) get_option('eep_event_gallery_type', 'default'))
            : 'default';
        $reporting_enabled = (string) get_option('eep_enable_event_reporting', '1') === '1';
        if ($single_org_mode) {
            $reporting_enabled = false;
        }
        $report_nonce_html = wp_nonce_field('eep_report_' . $post_id, 'eep_report_nonce', true, false);

        if ((string) get_post_status($post_id) === 'publish') {
            $viewer_identity = is_user_logged_in()
                ? 'u:' . (string) get_current_user_id()
                : 'ip:' . sanitize_text_field((string) ($_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'));
            $view_throttle_key = 'eep_view_' . md5((string) $post_id . '|' . $viewer_identity);
            if (!get_transient($view_throttle_key)) {
                set_transient($view_throttle_key, '1', 30 * MINUTE_IN_SECONDS);
                $this->record_analytics_event($post_id, 'listing_view', [
                    'context' => 'single',
                    'user_id' => is_user_logged_in() ? get_current_user_id() : 0,
                ]);
            }
        }

        $event_markup = $this->render_frontend_template('single-event.php', [
            'post_id' => $post_id,
            'event_title' => $event_title,
            'event_subtitle' => $excerpt,
            'content_html' => $content_html,
            'hero_image' => $cover_image,
            'hero_style_attr' => $hero_style_attr,
            'hero_kicker_text' => $hero_kicker_text,
            'hero_layout' => $hero_layout,
            'page_layout' => $page_layout,
            'event_gallery_type' => $event_gallery_type,
            'hero_date_label' => $hero_date_label,
            'show_hero_date_card' => $show_hero_date_card,
            'hero_location_label' => $hero_location_label,
            'local_time_start_iso' => $local_time_start_iso,
            'local_time_end_iso' => $local_time_end_iso,
            'event_timezone_label' => $timezone,
            'summary_cards' => $summary_cards,
            'overview_chips' => $overview_chips,
            'detail_rows' => $detail_rows,
            'show_event_overview' => $overview_has_content,
            'custom_fields_display' => $custom_fields_display,
            'sessions' => $sessions_rows,
            'show_timeline' => $show_timeline,
            'key_speakers' => $key_speakers_rows,
            'show_key_speakers' => $show_key_speakers,
            'gallery_images' => $gallery_display_urls,
            'video_cards' => $video_cards,
            'supporting_documents' => $supporting_document_cards,
            'map_link' => $map_link,
            'map_embed_url' => $map_embed_url,
            'venue_label' => $venue !== '' ? $venue : $event_title,
            'location_full' => $location_full,
            'status_text' => $status_text,
            'job_application_deadline' => $job_application_deadline,
            'countdown_target' => $countdown_target,
            'show_countdown' => $show_countdown,
            'external_url' => esc_url($external_url),
            'external_click_metric' => $external_click_metric,
            'calendar_url' => esc_url($calendar_url),
            'google_url' => esc_url($google_url),
            'viewer_logged_in' => $viewer_logged_in,
            'rsvp_enabled' => $viewer_logged_in,
            'show_rsvp_actions' => $show_rsvp_actions,
            'single_org_mode' => $single_org_mode,
            'show_calendar_actions' => $show_calendar_actions,
            'show_ticket_cta' => $show_ticket_cta,
            'ticket_checkout_url' => esc_url(admin_url('admin-post.php')),
            'ticket_checkout_nonce_html' => wp_nonce_field('eep_ticket_checkout_' . $post_id, 'eep_ticket_checkout_nonce', true, false),
            'ticket_default_gateway' => $ticket_gateway_default,
            'ticket_gateway_stripe_enabled' => $ticket_gateway_stripe_enabled,
            'ticket_gateway_paypal_enabled' => $ticket_gateway_paypal_enabled,
            'ticket_test_free_enabled' => $ticket_test_free_enabled,
            'ticket_button_label' => $ticket_button_label,
            'ticket_price_display' => $ticket_price_display,
            'ticket_currency' => $ticket_currency,
            'ticket_is_paid' => $ticket_is_paid,
            'rsvp_count' => count($rsvp),
            'waitlist_count' => count($waitlist),
            'whos_attending_url' => $whos_attending_url,
            'event_webapp_enabled' => $event_webapp_enabled,
            'event_webapp_url' => esc_url($event_webapp_url),
            'rsvp_intent' => $is_joined ? 'leave' : 'join',
            'rsvp_button_label' => $is_joined ? $single_rsvp_cancel : $single_rsvp_join,
            'rsvp_nonce_html' => wp_nonce_field('eep_rsvp_' . $post_id, 'eep_rsvp_nonce', true, false),
            'primary_action_url' => esc_url($primary_action_url),
            'primary_action_label' => $primary_action_label,
            'primary_action_metric' => $primary_action_metric,
            'directory_appointment_url' => esc_url($directory_appointment_url),
            'directory_whatsapp_url' => esc_url($directory_whatsapp_url),
            'directory_contact_person' => $directory_contact_person,
            'login_url' => $login_url,
            'signup_url' => $signup_url,
            'edit_event_url' => $edit_event_url,
            'edit_event_is_duplicate' => $edit_event_is_duplicate,
            'claimable' => $claimable,
            'claim_status' => $claim_status,
            'claim_url' => $claim_url,
            'show_autogenerated_disclaimer' => $show_autogenerated_disclaimer,
            'autogenerated_disclaimer_text' => $autogenerated_disclaimer_text,
            'autogenerated_source_url' => esc_url($autogenerated_source_url),
            'organizer_name' => $organizer_name,
            'organizer_id' => $organizer_id,
            'organizer_email' => $organizer_email,
            'organizer_verified' => $verified,
            'organizer_events_url' => $organizer_events_url,
            'organizer_member_since' => $organizer_member_since,
            'organizer_listing_count' => $organizer_listing_count,
            'show_organizer_card' => $show_organizer_card,
            'owner_event_messages_enabled' => $owner_event_messages_enabled,
            'contact_nonce_html' => wp_nonce_field('eep_contact_holder_' . $post_id, 'eep_contact_nonce', true, false),
            'contact_email' => $contact_email,
            'contact_phone' => $contact_phone,
            'market_accept_offers' => $market_accept_offers,
            'reporting_enabled' => $reporting_enabled,
            'report_nonce_html' => $report_nonce_html,
            'is_events_profile' => $is_events_profile,
            'is_job_profile' => $is_job_profile,
            'is_directory_profile' => $is_directory_profile,
            'is_marketplace_profile' => $is_marketplace_profile,
            'ui_labels' => $ui_labels,
            'single_overview_heading' => $single_overview_heading,
            'single_timeline_heading' => $single_timeline_heading,
            'single_gallery_heading' => $single_gallery_heading,
            'single_videos_heading' => $single_videos_heading,
            'single_cta_heading' => $single_cta_heading,
            'single_cta_copy' => $single_cta_copy,
            'single_visit_site_cta' => $single_visit_site_cta,
            'single_report_cta' => $single_report_cta,
            'single_report_placeholder' => $single_report_placeholder,
            'single_contact_login' => $single_contact_login,
            'single_owner_items_cta' => $single_owner_items_cta,
            'single_login_rsvp' => $single_login_rsvp,
            'single_edit_cta' => $single_edit_cta,
            'single_open_event_app_cta' => $single_open_event_app_cta,
        ]);

            return $top_injected_html . $this->render_notice() . $event_markup . $bottom_injected_html;
        } finally {
            $is_rendering_event_content = false;
        }
    }
}
