<?php
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.DirectDatabaseQuery.SchemaChange,WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value,WordPress.DB.SlowDBQuery.slow_db_query_meta_query,WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- NexDirectory uses trusted WordPress/custom table names and context-specific custom queries; user-controlled values are sanitized or prepared before use.

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

/**
 * Service for managing frontend URLs, routing, sticky navigation, auth UI, and breadcrumbs.
 */
class NEXDIRECTORY_Frontend_Navigation_Service {

    /**
     * @var string
     */
    private $post_type;

    /**
     * Constructor.
     */
    public function __construct($post_type = 'nexdirectory_event') {
        $this->post_type = sanitize_key($post_type);
    }

    /**
     * Enqueues a packaged stylesheet and returns late-print markup if needed.
     *
     * @param string $slug Stable handle suffix.
     * @param string $relative_path Path relative to the plugin root.
     * @return string
     */
    private function render_local_style_asset($slug, $relative_path) {
        $asset = $this->get_local_asset_reference($relative_path);
        if (empty($asset)) {
            return '';
        }

        $handle = sanitize_key('nexdirectory-' . (string) $slug);
        wp_enqueue_style($handle, $asset['url'], [], $asset['version']);
        if ((did_action('wp_head') || did_action('admin_head')) && !wp_style_is($handle, 'done')) {
            ob_start();
            wp_print_styles([$handle]);
            $markup = (string) ob_get_clean();
            if (class_exists('NEXDIRECTORY_Escaping', false)) {
                $markup = NEXDIRECTORY_Escaping::register_trusted_code_block($markup);
            }
            return $markup;
        }

        return '';
    }

    /**
     * Enqueues packaged JavaScript for the footer.
     *
     * @param string $slug Stable handle suffix.
     * @param string $relative_path Path relative to the plugin root.
     * @return string
     */
    private function render_local_script_asset($slug, $relative_path) {
        $asset = $this->get_local_asset_reference($relative_path);
        if (empty($asset)) {
            return '';
        }

        $handle = sanitize_key('nexdirectory-' . (string) $slug);
        wp_enqueue_script($handle, $asset['url'], [], $asset['version'], true);
        return '';
    }

    /**
     * Resolves a plugin-owned local asset.
     *
     * @param string $relative_path Path relative to the plugin root.
     * @return array{url:string,version:string}|array{}
     */
    private function get_local_asset_reference($relative_path) {
        $relative_path = ltrim(wp_normalize_path((string) $relative_path), '/');
        if ($relative_path === '' || strpos($relative_path, '..') !== false || !preg_match('/^[A-Za-z0-9_\\.\\/-]+$/', $relative_path)) {
            return [];
        }

        $root = plugin_dir_path(NEXDIRECTORY_PLUGIN_FILE);
        $path = wp_normalize_path($root . $relative_path);
        if (strpos($path, wp_normalize_path($root)) !== 0 || !is_readable($path)) {
            return [];
        }

        return [
            'url'     => plugins_url($relative_path, NEXDIRECTORY_PLUGIN_FILE),
            'version' => (string) filemtime($path),
        ];
    }

    /**
     * Registers inline CSS through WordPress and returns late-print output when needed.
     */
    private function render_inline_style_asset($slug, $css) {
        $css = trim((string) $css);
        if ($css === '') {
            return '';
        }

        $handle = sanitize_key('nexdirectory-' . (string) $slug);
        if (!wp_style_is($handle, 'registered')) {
            wp_register_style($handle, false, [], defined('NEXDIRECTORY_DB_VERSION') ? NEXDIRECTORY_DB_VERSION : null);
        }

        wp_enqueue_style($handle);
        wp_add_inline_style($handle, wp_strip_all_tags($css));

        if ((did_action('wp_head') || did_action('admin_head')) && !wp_style_is($handle, 'done')) {
            ob_start();
            wp_print_styles([$handle]);
            $markup = (string) ob_get_clean();
            if (class_exists('NEXDIRECTORY_Escaping', false)) {
                $markup = NEXDIRECTORY_Escaping::register_trusted_code_block($markup);
            }
            return $markup;
        }

        return '';
    }

    /**
     * Registers inline JavaScript through WordPress.
     *
     * Scripts are registered for the footer, so they remain valid JavaScript instead of
     * being passed back through HTML sanitization as part of a rendered component.
     */
    private function render_inline_script_asset($slug, $script) {
        $script = trim((string) $script);
        if ($script === '') {
            return '';
        }
        $script = str_replace('</script', '<\/script', wp_check_invalid_utf8($script));

        $handle = sanitize_key('nexdirectory-' . (string) $slug);
        if (!wp_script_is($handle, 'registered')) {
            wp_register_script($handle, false, [], defined('NEXDIRECTORY_DB_VERSION') ? NEXDIRECTORY_DB_VERSION : null, true);
        }

        wp_enqueue_script($handle);
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Inline JavaScript is plugin-authored, UTF-8 checked above, and printed by WordPress.
        wp_add_inline_script($handle, $script, 'after');

        return '';
    }

    /**
     * Helper to reliably check boolean/toggle options.
     */
    public function is_option_enabled($option_key, $default = '0') {
        $raw = get_option($option_key, $default);
        if (is_bool($raw)) return $raw;
        if (is_numeric($raw)) return ((float) $raw) !== 0.0;
        
        $normalized = strtolower(trim((string) $raw));
        if ($normalized === '') {
            $normalized = strtolower(trim((string) $default));
        }
        return in_array($normalized, ['1', 'true', 'yes', 'on', 'enabled'], true);
    }

    /**
     * Returns the active profile's core entity labels.
     *
     * @return array<string, string>
     */
    private function get_profile_label_context() {
        $profile_key = sanitize_key((string) get_option('nexdirectory_site_profile', 'events'));
        $profiles = [
            'events' => [
                'singular' => __('Event', 'nexdirectory'),
                'plural' => __('Events', 'nexdirectory'),
                'owner_singular' => __('Organizer', 'nexdirectory'),
                'owner_plural' => __('Organizers', 'nexdirectory'),
                'classification_entity' => __('Event', 'nexdirectory'),
            ],
            'directory' => [
                'singular' => __('Listing', 'nexdirectory'),
                'plural' => __('Listings', 'nexdirectory'),
                'owner_singular' => __('Owner', 'nexdirectory'),
                'owner_plural' => __('Owners', 'nexdirectory'),
                'classification_entity' => __('Listing', 'nexdirectory'),
            ],
            'job_board' => [
                'singular' => __('Job', 'nexdirectory'),
                'plural' => __('Jobs', 'nexdirectory'),
                'owner_singular' => __('Employer', 'nexdirectory'),
                'owner_plural' => __('Employers', 'nexdirectory'),
                'classification_entity' => __('Job Listing', 'nexdirectory'),
            ],
            'marketplace' => [
                'singular' => __('Listing', 'nexdirectory'),
                'plural' => __('Listings', 'nexdirectory'),
                'owner_singular' => __('Seller', 'nexdirectory'),
                'owner_plural' => __('Sellers', 'nexdirectory'),
                'classification_entity' => __('Marketplace Listing', 'nexdirectory'),
            ],
        ];
        if (!isset($profiles[$profile_key])) {
            $profile_key = 'directory';
        }
        return array_merge(['profile_key' => $profile_key], $profiles[$profile_key]);
    }

    /**
     * Returns the small profile label subset used by admin UI services.
     *
     * @return array<string, string>
     */
    public function get_site_profile_ui_labels() {
        $profile = $this->get_profile_label_context();
        return [
            'profile_key' => (string) $profile['profile_key'],
            'is_events_profile' => (string) $profile['profile_key'] === 'events' ? '1' : '0',
            'singular' => sanitize_text_field((string) $profile['singular']),
            'plural' => sanitize_text_field((string) $profile['plural']),
            'owner_singular' => sanitize_text_field((string) $profile['owner_singular']),
            'owner_plural' => sanitize_text_field((string) $profile['owner_plural']),
            'classification_entity' => sanitize_text_field((string) $profile['classification_entity']),
            'single_classification_label' => __('Classification', 'nexdirectory'),
        ];
    }

    /**
     * Returns profile-aware labels for official listing classifications.
     *
     * @return array<string, string>
     */
    public function get_classification_labels() {
        $profile = $this->get_profile_label_context();
        $entity = sanitize_text_field((string) $profile['classification_entity']);
        $configured = sanitize_text_field((string) get_option('nexdirectory_official_classification_suffix', ''));
        $base = trim((string) preg_replace('/^(?:un)?official\s+/i', '', $configured));
        if ($base === '') {
            $base = $entity;
        }

        /* translators: %s: Directory entity label, such as event or business. */
        $official_fallback = sprintf(__('Official %s', 'nexdirectory'), $base);
        /* translators: %s: Directory entity label, such as event or business. */
        $unofficial_fallback = sprintf(__('Unofficial %s', 'nexdirectory'), $base);
        $official = $configured !== '' && preg_match('/^official\b/i', $configured)
            ? $configured
            : $official_fallback;
        $unofficial = $configured !== '' && preg_match('/^unofficial\b/i', $configured)
            ? $configured
            : $unofficial_fallback;

        return [
            'official' => sanitize_text_field((string) $official),
            'unofficial' => sanitize_text_field((string) $unofficial),
            'official_short' => $configured !== '' ? sanitize_text_field((string) $official) : __('Official', 'nexdirectory'),
            'unofficial_short' => $configured !== '' ? sanitize_text_field((string) $unofficial) : __('Unofficial', 'nexdirectory'),
        ];
    }

    /**
     * Shared brand-name fallback used by frontend navigation.
     */
    private function get_platform_brand_name() {
        $brand_name = sanitize_text_field((string) get_option('nexdirectory_company_brand_name', ''));
        if ($brand_name === '') {
            $brand_name = sanitize_text_field((string) get_option('blogname', get_bloginfo('name')));
        }
        if ($brand_name === '') {
            $brand_name = __('Nex Directory', 'nexdirectory');
        }

        $brand_name = sanitize_text_field((string) apply_filters('nexdirectory_public_brand_name', $brand_name));
        return $brand_name !== '' ? $brand_name : __('Nex Directory', 'nexdirectory');
    }

    /**
     * Normalizes the global page side padding option.
     *
     * Keeps sticky navigation and page wrappers aligned to the same value.
     *
     * @param string $default
     * @return string
     */
    public function get_page_side_padding_value($default = '10%') {
        $normalized_default = $this->normalize_page_side_padding_value($default, '10%');
        return $this->normalize_page_side_padding_value(
            (string) get_option('nexdirectory_page_side_padding', $normalized_default),
            $normalized_default
        );
    }

    /**
     * Mobile padding value. Empty option falls back to desktop padding.
     *
     * @param string $default
     * @return string
     */
    public function get_page_side_padding_mobile_value($default = '2%') {
        $desktop = $this->get_page_side_padding_value('10%');
        $fallback = trim((string) $default) !== '' ? (string) $default : $desktop;
        $fallback = $this->normalize_page_side_padding_value($fallback, $desktop);

        $raw = trim((string) get_option('nexdirectory_page_side_padding_mobile', '2%'));
        if ($raw === '') {
            return $fallback;
        }

        return $this->normalize_page_side_padding_value($raw, $fallback);
    }

    /**
     * Desktop sticky navbar side padding.
     * Empty option inherits desktop page side padding.
     *
     * @param string $default
     * @return string
     */
    public function get_nav_side_padding_desktop_value($default = '0%') {
        $desktop = $this->get_page_side_padding_value('10%');
        $fallback = trim((string) $default) !== '' ? (string) $default : $desktop;
        $fallback = $this->normalize_page_side_padding_value($fallback, $desktop);

        $raw = trim((string) get_option('nexdirectory_nav_side_padding_desktop', '0%'));
        if ($raw === '') {
            return $desktop;
        }

        return $this->normalize_page_side_padding_value($raw, $fallback);
    }

    /**
     * Normalizes percent-based page padding values and clamps to 0..15%.
     *
     * @param string $value
     * @param string $fallback
     * @return string
     */
    private function normalize_page_side_padding_value($value, $fallback = '10%') {
        $value = strtolower(trim((string) $value));
        $fallback = strtolower(trim((string) $fallback));
        if ($fallback === '') {
            $fallback = '10%';
        }
        if ($value === '') {
            $value = $fallback;
        }
        if (is_numeric($value)) {
            $value .= '%';
        }

        if (!preg_match('/^(\d+(?:\.\d+)?)%$/', $value, $matches)) {
            $value = $fallback;
            if (!preg_match('/^(\d+(?:\.\d+)?)%$/', $value, $matches)) {
                $matches = ['10%', '10'];
            }
        }

        $padding = (float) ($matches[1] ?? 10);
        if ($padding < 0) {
            $padding = 0;
        } elseif ($padding > 15) {
            $padding = 15;
        }

        if (abs($padding - (float) ((int) $padding)) < 0.000001) {
            $normalized = (string) ((int) $padding);
        } else {
            $normalized = rtrim(rtrim(sprintf('%.6F', $padding), '0'), '.');
        }
        if ($normalized === '') {
            $normalized = '0';
        }

        return $normalized . '%';
    }

    /**
     * Retrieves the current safe URL.
     */
    public function current_url() {
        $request_uri = '/';
        if (isset($_SERVER['REQUEST_URI'])) {
            $candidate = trim(sanitize_text_field((string) wp_unslash($_SERVER['REQUEST_URI'])));
            if ($candidate !== '') $request_uri = $candidate;
        }
        if ($request_uri === '' || strpos($request_uri, '/') !== 0) {
            $request_uri = '/' . ltrim($request_uri, '/');
        }
        return esc_url_raw(home_url($request_uri));
    }

    /**
     * Returns a frontend login-required prompt with a safe login URL.
     *
     * This method is used by shortcode renderers that need to stop output for
     * unauthenticated users without hard-killing the request.
     *
     * @param string $message
     * @return string
     */
    public function redirect_to_login_screen($message = '') {
        $notice = sanitize_text_field((string) $message);
        if ($notice === '') {
            $notice = sanitize_text_field((string) __('Please log in to continue.', 'nexdirectory'));
        }

        $current = $this->current_url();
        $login_url = $this->get_platform_page_url(
            'nexdirectory_page_login_signup_url',
            ['auth', 'login-signup'],
            wp_login_url($current)
        );
        if (!is_string($login_url) || trim($login_url) === '') {
            $login_url = wp_login_url($current);
        } else {
            $login_url = nexdirectory_add_query_arg('redirect_to', $current, $login_url);
        }

        return '<section class="nexdirectory-page-banner"><h1>'
            . esc_html__('Login required', 'nexdirectory')
            . '</h1><p>'
            . esc_html($notice)
            . '</p></section><div class="nexdirectory-submit-wrap"><section class="nexdirectory-form-card"><p>'
            . esc_html__('Sign in to continue this action.', 'nexdirectory')
            . '</p><p><a class="nexdirectory-btn" data-nexdirectory-auth-open="1" data-nexdirectory-auth-tab="login" href="'
            . esc_url($login_url)
            . '">'
            . esc_html__('Go to Login / Signup', 'nexdirectory')
            . '</a></p></section></div>';
    }

    /**
     * Redirects to a URL and appends a normalized frontend notice payload.
     *
     * @param string $base_url
     * @param string $message
     * @param string $type
     * @return void
     */
    public function redirect_with_message($base_url, $message, $type = 'success') {
        $target = esc_url_raw((string) $base_url);
        if ($target === '') {
            $target = esc_url_raw(home_url('/'));
        }

        $notice = sanitize_text_field((string) $message);
        if ($notice === '') {
            $notice = sanitize_text_field((string) __('Action completed.', 'nexdirectory'));
        }

        $notice_type = sanitize_key((string) $type);
        if (!in_array($notice_type, ['success', 'error', 'warning', 'info', 'warn'], true)) {
            $notice_type = 'success';
        }
        if ($notice_type === 'warn') {
            $notice_type = 'warning';
        }

        $url = nexdirectory_add_query_arg(
            [
                'nexdirectory_notice' => rawurlencode($notice),
                'nexdirectory_notice_type' => $notice_type,
            ],
            $target
        );

        $is_ajax_notice = sanitize_key((string) wp_unslash($_REQUEST['nexdirectory_ajax'] ?? '')) === '1'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        if ($is_ajax_notice) {
            $payload = [
                'message' => $notice,
                'type' => $notice_type,
                'redirect' => $url,
            ];
            if (in_array($notice_type, ['error', 'warning'], true)) {
                wp_send_json_error($payload, $notice_type === 'error' ? 400 : 200);
            }
            wp_send_json_success($payload);
        }

        $this->safe_redirect_or_fallback($url);
    }

    /**
     * Renders redirect notice payload for frontend toaster consumption.
     *
     * @return string
     */
    public function render_notice() {
        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Notices are read-only redirect payloads.
        if (empty($_GET['nexdirectory_notice'])) {
            return '';
        }

        $notice = sanitize_text_field((string) wp_unslash($_GET['nexdirectory_notice']));
        if ($notice === '') {
            return '';
        }

        $type = !empty($_GET['nexdirectory_notice_type']) ? sanitize_key((string) wp_unslash($_GET['nexdirectory_notice_type'])) : 'success';
        if ($type === 'warn') {
            $type = 'warning';
        }
        if (!in_array($type, ['success', 'error', 'warning', 'info'], true)) {
            $type = 'success';
        }

        return sprintf(
            '<div data-nexdirectory-toast-message="%1$s" data-nexdirectory-toast-type="%2$s" style="display:none !important;" aria-hidden="true"></div><noscript><div class="nexdirectory-notice nexdirectory-notice-%2$s">%3$s</div></noscript>',
            esc_attr($notice),
            esc_attr($type),
            esc_html($notice)
        );
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
    }

    /**
     * Safe redirect that falls back to JS/Meta refresh if headers are already sent.
     */
    public function safe_redirect_or_fallback($url, $status = 302) {
        $url = esc_url_raw((string) $url);
        if ($url === '') return;
        
        $status = (int) $status;
        if ($status < 300 || $status > 399) {
            $status = 302;
        }

        $target_host = strtolower((string) wp_parse_url($url, PHP_URL_HOST));
        $site_host = strtolower((string) wp_parse_url(home_url('/'), PHP_URL_HOST));
        $allowed_external_hosts = array_values(array_unique(array_filter(array_map('strtolower', (array) apply_filters(
            'nexdirectory_allowed_external_redirect_hosts',
            []
        )))));
        $is_external_gateway_redirect = $target_host !== '' && $site_host !== '' && $target_host !== $site_host && in_array($target_host, $allowed_external_hosts, true);

        if (!headers_sent()) {
            $allow_host_filter = null;
            if ($is_external_gateway_redirect && $target_host !== '') {
                $allow_host_filter = static function ($hosts) use ($target_host) {
                    $hosts = is_array($hosts) ? $hosts : [];
                    if (!in_array($target_host, $hosts, true)) {
                        $hosts[] = $target_host;
                    }
                    return $hosts;
                };
                add_filter('allowed_redirect_hosts', $allow_host_filter);
            }
            wp_safe_redirect($url, $status);
            if ($allow_host_filter !== null) {
                remove_filter('allowed_redirect_hosts', $allow_host_filter);
            }
            exit;
        }

        wp_print_inline_script_tag('window.location.replace(' . wp_json_encode($url) . ');');
        echo '<noscript><meta http-equiv="refresh" content="0;url=' . esc_url($url) . '"></noscript>';
        exit;
    }

    /**
     * Generates a dynamic platform page URL, recovering via shortcodes if the slug is broken.
     */
    public function get_platform_page_url($option_key, $fallback_slug = '', $fallback_url = '') {
        $option_key = sanitize_key((string) $option_key);
        $fallback_slugs = is_array($fallback_slug) ? $fallback_slug : [$fallback_slug];
        $fallback_slugs = array_values(array_unique(array_filter(array_map('nexdirectory_sanitize_title', $fallback_slugs))));
        $preferred_slug = isset($fallback_slugs[0]) ? (string) $fallback_slugs[0] : '';
        $configured = $option_key !== '' ? esc_url_raw((string) get_option($option_key, '')) : '';
        $fallback_url = esc_url_raw((string) $fallback_url);
        static $request_cache = [];
        $cache_key = md5(wp_json_encode([$option_key, $fallback_slugs, $fallback_url, $configured]));
        if (array_key_exists($cache_key, $request_cache)) {
            return $request_cache[$cache_key];
        }
        $is_configured_root = $configured !== '' && $this->is_site_root_url($configured);

        // 1. Check explicitly configured page option
        if ($configured !== '' && !$is_configured_root) {
            $configured_page_id = absint((int) url_to_postid($configured));
            if ($configured_page_id > 0) {
                if ((string) get_post_status($configured_page_id) === 'publish') {
                    $configured_permalink = get_permalink($configured_page_id);
                    if (is_string($configured_permalink) && $configured_permalink !== '') {
                        $request_cache[$cache_key] = esc_url_raw((string) $configured_permalink);
                        return $request_cache[$cache_key];
                    }
                }
            }
            $request_cache[$cache_key] = $configured;
            return $request_cache[$cache_key];
        }

        // 2. Check fallback slugs
        foreach ($fallback_slugs as $fallback_slug_item) {
            $fallback_slug_value = sanitize_title((string) $fallback_slug_item);
            if ($fallback_slug_value === '') continue;
            
            $page = get_page_by_path($fallback_slug_value);
            if ($page instanceof WP_Post && !is_wp_error($page) && (string) get_post_status((int) $page->ID) === 'publish') {
                $url = get_permalink((int) $page->ID);
                if (is_string($url) && $url !== '') {
                    $request_cache[$cache_key] = esc_url_raw((string) $url);
                    return $request_cache[$cache_key];
                }
            }
        }

        // 3. Database lookup for shortcode
        $shortcode_map = [
            'nexdirectory_page_account_url'         => 'nexdirectory_account',
            'nexdirectory_page_login_signup_url'    => 'nexdirectory_auth',
            'nexdirectory_page_submit_event_url'    => 'nexdirectory_submit',
            'nexdirectory_page_my_events_url'       => 'nexdirectory_my_listings',
            'nexdirectory_page_events_url'          => 'nexdirectory_listings_archive',
            'nexdirectory_page_events_map_url'      => 'nexdirectory_listings_map',
            'nexdirectory_page_events_calendar_url' => 'nexdirectory_listings_calendar',
            'nexdirectory_page_event_webapp_url'    => 'nexdirectory_event_webapp',
            'nexdirectory_page_my_tickets_url'      => 'nexdirectory_saved_listings',
            'nexdirectory_page_whos_attending_url'  => 'nexdirectory_whos_attending',
            'nexdirectory_page_notifications_url'   => 'nexdirectory_notifications',
            'nexdirectory_page_whats_new_url'       => 'nexdirectory_whats_new',
            'nexdirectory_claim_listing_page_url'   => 'nexdirectory_claim_listing',
        ];
        $shortcode_map = (array) apply_filters('nexdirectory_extension_navigation_shortcode_map', $shortcode_map, $option_key);
        
        $shortcode_tag = isset($shortcode_map[$option_key]) ? sanitize_key((string) $shortcode_map[$option_key]) : '';
        if ($shortcode_tag !== '') {
            global $wpdb;
            $like = '%' . $wpdb->esc_like('[' . $shortcode_tag) . '%';
            $shortcode_page_id = (int) $wpdb->get_var($wpdb->prepare(
                "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'page' AND post_status = 'publish' AND post_content LIKE %s ORDER BY ID ASC LIMIT 1",
                $like
            ));
            if ($shortcode_page_id > 0) {
                $shortcode_url = get_permalink($shortcode_page_id);
                if (is_string($shortcode_url) && $shortcode_url !== '') {
                    $request_cache[$cache_key] = esc_url_raw((string) $shortcode_url);
                    return $request_cache[$cache_key];
                }
            }
        }

        // 4. Hard fallback
        $request_cache[$cache_key] = $fallback_url !== '' ? $fallback_url : esc_url_raw((string) home_url('/'));
        return $request_cache[$cache_key];
    }

    /**
     * Whether a URL points to the site root path ("/"), which is invalid for route options.
     *
     * @param string $url
     * @return bool
     */
    private function is_site_root_url($url) {
        $url = esc_url_raw((string) $url);
        if ($url === '') {
            return false;
        }
        $path = (string) wp_parse_url($url, PHP_URL_PATH);
        $path = trim($path);
        if ($path === '' || $path === '/') {
            return true;
        }
        return untrailingslashit($path) === '';
    }

    /**
     * Resolves the custom login page URL.
     */
    public function get_custom_login_page_url() {
        $configured = esc_url_raw((string) get_option('nexdirectory_page_login_signup_url', ''));
        if ($configured !== '') return $configured;

        $fallback_page = get_page_by_path('login-signup');
        if ($fallback_page instanceof WP_Post && (string) $fallback_page->post_status === 'publish') {
            $url = get_permalink((int) $fallback_page->ID);
            if (is_string($url) && $url !== '') {
                return esc_url_raw((string) $url);
            }
        }
        return '';
    }

    /**
     * Legacy-compatible helper for building login/signup URL with redirect target.
     */
    public function get_login_signup_url($redirect_to = '') {
        $redirect_to = esc_url_raw((string) $redirect_to);
        if ($redirect_to === '') {
            $redirect_to = $this->current_url();
        }

        $login_base = $this->get_custom_login_page_url();
        if ($login_base === '') {
            return wp_login_url($redirect_to);
        }

        return nexdirectory_add_query_arg('redirect_to', $redirect_to, $login_base);
    }

    /**
     * Keeps WordPress core login URLs available for security/plugin compatibility.
     */
    public function filter_wp_login_url($login_url, $redirect = '', $force_reauth = false) {
        return $login_url;
    }

    /**
     * Preserves the native wp-login.php screen.
     */
    public function maybe_redirect_wp_login_screen() {
        return;
    }

    /**
     * Renders frontend platform breadcrumbs.
     */
    public function render_platform_breadcrumbs($current_label = '', $plural_label = 'Items', $archive_url = '') {
        $items = [];
        $items[] = [
            'label' => __('Home', 'nexdirectory'),
            'url' => home_url('/'),
        ];

        $current_label = sanitize_text_field((string) $current_label);
        
        if (is_singular($this->post_type)) {
            $items[] = [
                'label' => $plural_label,
                'url' => $archive_url,
            ];
            $event_title = $current_label !== '' ? $current_label : get_the_title(get_the_ID());
            if ($event_title !== '') {
                $items[] = ['label' => sanitize_text_field((string) $event_title), 'url' => ''];
            }
        } elseif (is_page()) {
            $page_id = (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;
                    
                    $label = get_the_title($ancestor_id);
                    $url = get_permalink($ancestor_id);
                    if ($label !== '' && is_string($url) && $url !== '') {
                        $items[] = ['label' => sanitize_text_field((string) $label), 'url' => esc_url_raw((string) $url)];
                    }
                }
                $normalized_archive_url = esc_url_raw((string) $archive_url);
                if ($normalized_archive_url !== '' && $normalized_archive_url !== '#') {
                    $items[] = [
                        'label' => sanitize_text_field((string) $plural_label),
                        'url' => $normalized_archive_url,
                    ];
                }
                $page_title = $current_label !== '' ? $current_label : get_the_title($page_id);
                if ($page_title !== '') {
                    $items[] = ['label' => sanitize_text_field((string) $page_title), 'url' => ''];
                }
            }
        } else {
            $archive_label = $current_label;
            if ($archive_label === '' && function_exists('get_the_archive_title')) {
                $archive_label = wp_strip_all_tags((string) get_the_archive_title());
            }
            if ($archive_label !== '') {
                $items[] = ['label' => sanitize_text_field((string) $archive_label), 'url' => ''];
            }
        }

        if (count($items) < 2) return '';

        ob_start();
        ?>
        <div class="nexdirectory-breadcrumb-shell">
            <nav class="nexdirectory-breadcrumbs" aria-label="<?php esc_attr_e('Breadcrumb', 'nexdirectory'); ?>">
                <?php $item_count = count($items); ?>
                <?php foreach ($items as $index => $item) : ?>
                    <?php
                    $label = sanitize_text_field((string) ($item['label'] ?? ''));
                    if ($label === '') continue;
                    $url = esc_url_raw((string) ($item['url'] ?? ''));
                    $is_last = ((int) $index === (int) ($item_count - 1));
                    ?>
                    <?php if (!$is_last && $url !== '') : ?>
                        <a href="<?php echo esc_url($url); ?>"><?php echo esc_html($label); ?></a>
                        <span class="nexdirectory-breadcrumb-sep" aria-hidden="true">/</span>
                    <?php else : ?>
                        <span class="is-current" aria-current="page"><?php echo esc_html($label); ?></span>
                        <?php if (!$is_last) : ?>
                            <span class="nexdirectory-breadcrumb-sep" aria-hidden="true">/</span>
                        <?php endif; ?>
                    <?php endif; ?>
                <?php endforeach; ?>
            </nav>
        </div>
        <?php
        return (string) ob_get_clean();
    }

    /**
     * Evaluates whether the sticky nav should be shown based on user state and settings.
     */
    public function should_show_frontend_sticky_nav_for_current_user() {
        if (!$this->is_option_enabled('nexdirectory_enable_frontend_sticky_nav', '1')) {
            return false;
        }
        if (is_user_logged_in()) {
            return $this->is_option_enabled('nexdirectory_frontend_sticky_nav_show_for_members', '1');
        }
        return $this->is_option_enabled('nexdirectory_frontend_sticky_nav_show_for_guests', '1');
    }

    /**
     * Determines if the sticky nav is already handling auth rendering.
     */
    public function sticky_nav_handles_auth_cta_for_current_user() {
        return $this->should_show_frontend_sticky_nav_for_current_user()
            && $this->is_option_enabled('nexdirectory_frontend_sticky_nav_include_auth_cta', '1');
    }

    /**
     * Extracts custom sticky nav links from plugin settings.
     */
    public function parse_frontend_sticky_nav_custom_links() {
        $raw = (string) get_option('nexdirectory_frontend_sticky_nav_custom_links', '');
        $lines = preg_split('/\r\n|\r|\n/', $raw) ?: [];
        $links = [];
        
        foreach ($lines as $line) {
            $line = trim((string) $line);
            if ($line === '' || strpos($line, '#') === 0) continue;
            
            $parts = array_map('trim', explode('|', $line));
            $label = sanitize_text_field((string) ($parts[0] ?? ''));
            $url = esc_url_raw((string) ($parts[1] ?? ''));
            $audience = sanitize_key((string) ($parts[2] ?? 'all'));
            $group = trim((string) str_replace('|', ' ', sanitize_text_field((string) ($parts[3] ?? __('More', 'nexdirectory')))));
            
            if ($group === '') $group = __('More', 'nexdirectory');
            if ($audience === 'loggedin') $audience = 'member';
            elseif ($audience === 'loggedout') $audience = 'guest';
            if (!in_array($audience, ['all', 'guest', 'member'], true)) $audience = 'all';
            
            if ($label === '' || $url === '') continue;
            
            $links[] = [
                'label' => $label,
                'url' => $url,
                'audience' => $audience,
                'group' => $group,
            ];
            
            if (count($links) >= 16) break;
        }
        return $links;
    }

    /**
     * Resolves selected WP menu ID for the current visitor state.
     *
     * Priority:
     * guest -> guest menu
     * admin -> admin menu
     * role override -> role-specific menu
     * member fallback -> member menu
     *
     * @return int
     */
    private function get_frontend_sticky_nav_wp_menu_id_for_current_user() {
        if (!$this->is_option_enabled('nexdirectory_frontend_sticky_nav_use_wp_menus', '0')) {
            return 0;
        }

        if (!is_user_logged_in()) {
            return absint((int) get_option('nexdirectory_frontend_sticky_nav_wp_menu_guest', 0));
        }

        $admin_menu_id = absint((int) get_option('nexdirectory_frontend_sticky_nav_wp_menu_admin', 0));
        if ($admin_menu_id > 0 && current_user_can('manage_options')) {
            return $admin_menu_id;
        }

        $role_menu_map = get_option('nexdirectory_frontend_sticky_nav_wp_menu_roles', []);
        if (is_array($role_menu_map)) {
            $user = wp_get_current_user();
            if ($user instanceof WP_User && !empty($user->roles) && is_array($user->roles)) {
                foreach ($user->roles as $role_slug_raw) {
                    $role_slug = sanitize_key((string) $role_slug_raw);
                    if ($role_slug === '') {
                        continue;
                    }
                    $role_menu_id = isset($role_menu_map[$role_slug]) ? absint((int) $role_menu_map[$role_slug]) : 0;
                    if ($role_menu_id > 0) {
                        return $role_menu_id;
                    }
                }
            }
        }

        return absint((int) get_option('nexdirectory_frontend_sticky_nav_wp_menu_member', 0));
    }

    /**
     * Normalizes WP menu URLs, handling relative links and special hash placeholders.
     *
     * @param string $raw_url
     * @return string
     */
    private function normalize_frontend_sticky_nav_wp_menu_url($raw_url) {
        $url = trim((string) $raw_url);
        if ($url === '') {
            return '';
        }
        if ($url === '#') {
            return '#';
        }
        if (strpos($url, '/') === 0) {
            return esc_url_raw(home_url($url));
        }
        if (preg_match('/^[a-z][a-z0-9+\-.]*:/i', $url)) {
            return esc_url_raw($url);
        }
        return esc_url_raw(home_url('/' . ltrim($url, '/')));
    }

    /**
     * Recursively flattens child items under a WP menu parent into dropdown links.
     *
     * @param int $parent_id
     * @param array<int, array<int, array<string, mixed>>> $children_map
     * @param int $depth
     * @param int $max_depth
     * @return array<int, array<string, string>>
     */
    private function flatten_frontend_sticky_nav_wp_menu_children($parent_id, $children_map, $depth = 0, $max_depth = 2) {
        if ($depth > $max_depth) {
            return [];
        }

        $parent_id = absint((int) $parent_id);
        $children = isset($children_map[$parent_id]) && is_array($children_map[$parent_id]) ? $children_map[$parent_id] : [];
        if (empty($children)) {
            return [];
        }

        $links = [];
        foreach ($children as $child) {
            if (!is_array($child)) {
                continue;
            }
            $label = sanitize_text_field((string) ($child['label'] ?? ''));
            $url = esc_url_raw((string) ($child['url'] ?? ''));
            $id = absint((int) ($child['id'] ?? 0));
            if ($id <= 0 || $label === '') {
                continue;
            }
            if ($url !== '' && $url !== '#') {
                if ($depth > 0) {
                    $label = str_repeat('-- ', $depth) . $label;
                }
                $links[] = [
                    'label' => $label,
                    'url' => $url,
                ];
            }
            $descendants = $this->flatten_frontend_sticky_nav_wp_menu_children($id, $children_map, $depth + 1, $max_depth);
            foreach ($descendants as $descendant_link) {
                if (!is_array($descendant_link)) {
                    continue;
                }
                $desc_label = sanitize_text_field((string) ($descendant_link['label'] ?? ''));
                $desc_url = esc_url_raw((string) ($descendant_link['url'] ?? ''));
                if ($desc_label === '' || $desc_url === '') {
                    continue;
                }
                $links[] = [
                    'label' => $desc_label,
                    'url' => $desc_url,
                ];
            }
        }

        return $links;
    }

    /**
     * Builds sticky nav menu groups from a selected WordPress menu.
     *
     * Top-level menu items become group labels when they have children.
     * Top-level standalone links are collected under a generic "Menu" group.
     *
     * @param int $menu_id
     * @return array<int, array<string, mixed>>
     */
    private function get_frontend_sticky_nav_menu_groups_from_wp_menu($menu_id) {
        $menu_id = absint((int) $menu_id);
        if ($menu_id <= 0) {
            return [];
        }

        $menu_obj = wp_get_nav_menu_object($menu_id);
        if (!$menu_obj) {
            return [];
        }

        $menu_items = wp_get_nav_menu_items($menu_id, ['update_post_term_cache' => false]);
        if (!is_array($menu_items) || empty($menu_items)) {
            return [];
        }

        $indexed_items = [];
        foreach ($menu_items as $menu_item) {
            if (!is_object($menu_item)) {
                continue;
            }
            $item_id = absint((int) ($menu_item->ID ?? 0));
            if ($item_id <= 0) {
                continue;
            }

            $item_label = sanitize_text_field((string) wp_strip_all_tags((string) ($menu_item->title ?? '')));
            if ($item_label === '') {
                continue;
            }

            $indexed_items[$item_id] = [
                'id' => $item_id,
                'label' => $item_label,
                'url' => $this->normalize_frontend_sticky_nav_wp_menu_url((string) ($menu_item->url ?? '')),
                'parent_id' => absint((int) ($menu_item->menu_item_parent ?? 0)),
                'menu_order' => (int) ($menu_item->menu_order ?? 0),
            ];
        }

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

        uasort($indexed_items, static function ($a, $b) {
            $a_order = (int) ($a['menu_order'] ?? 0);
            $b_order = (int) ($b['menu_order'] ?? 0);
            if ($a_order === $b_order) {
                return ((int) ($a['id'] ?? 0)) <=> ((int) ($b['id'] ?? 0));
            }
            return $a_order <=> $b_order;
        });

        $children_map = [];
        foreach ($indexed_items as $item) {
            $parent_id = absint((int) ($item['parent_id'] ?? 0));
            if ($parent_id > 0 && isset($indexed_items[$parent_id])) {
                if (!isset($children_map[$parent_id]) || !is_array($children_map[$parent_id])) {
                    $children_map[$parent_id] = [];
                }
                $children_map[$parent_id][] = $item;
            }
        }
        foreach ($children_map as &$children) {
            if (!is_array($children) || count($children) < 2) {
                continue;
            }
            usort($children, static function ($a, $b) {
                $a_order = (int) ($a['menu_order'] ?? 0);
                $b_order = (int) ($b['menu_order'] ?? 0);
                if ($a_order === $b_order) {
                    return ((int) ($a['id'] ?? 0)) <=> ((int) ($b['id'] ?? 0));
                }
                return $a_order <=> $b_order;
            });
        }
        unset($children);

        $groups = [];
        $standalone_links = [];
        foreach ($indexed_items as $item) {
            $parent_id = absint((int) ($item['parent_id'] ?? 0));
            if ($parent_id !== 0) {
                continue;
            }

            $item_id = absint((int) ($item['id'] ?? 0));
            if ($item_id <= 0) {
                continue;
            }

            $child_links = $this->flatten_frontend_sticky_nav_wp_menu_children($item_id, $children_map, 0, 2);
            if (!empty($child_links)) {
                $group_links = [];
                $item_url = esc_url_raw((string) ($item['url'] ?? ''));
                if ($item_url !== '' && $item_url !== '#') {
                    $group_links[] = [
                        'label' => sanitize_text_field((string) ($item['label'] ?? '')),
                        'url' => $item_url,
                    ];
                }
                foreach ($child_links as $child_link) {
                    if (!is_array($child_link)) {
                        continue;
                    }
                    $child_label = sanitize_text_field((string) ($child_link['label'] ?? ''));
                    $child_url = esc_url_raw((string) ($child_link['url'] ?? ''));
                    if ($child_label === '' || $child_url === '') {
                        continue;
                    }
                    $group_links[] = [
                        'label' => $child_label,
                        'url' => $child_url,
                    ];
                }
                if (!empty($group_links)) {
                    $groups[] = [
                        'label' => sanitize_text_field((string) ($item['label'] ?? '')),
                        'links' => $group_links,
                    ];
                }
                continue;
            }

            $item_url = esc_url_raw((string) ($item['url'] ?? ''));
            if ($item_url === '' || $item_url === '#') {
                continue;
            }
            $standalone_links[] = [
                'label' => sanitize_text_field((string) ($item['label'] ?? '')),
                'url' => $item_url,
            ];
        }

        if (!empty($standalone_links)) {
            array_unshift($groups, [
                'label' => __('Menu', 'nexdirectory'),
                'links' => $standalone_links,
            ]);
        }

        return $this->filter_boost_links_from_menu_groups($groups);
    }

    /**
     * Checks whether current request is one of the plugin app pages (shortcode pages).
     *
     * @param WP_Post|int|null $post
     * @return bool
     */
    public function is_app_page($post = null) {
        $resolved_post = $this->resolve_app_page_post($post);
        if (!$resolved_post instanceof WP_Post) {
            return false;
        }

        $is_marked = (string) get_post_meta((int) $resolved_post->ID, '_nexdirectory_is_app_page', true);
        if ($is_marked === '1') {
            return true;
        }

        if ($this->get_app_view($resolved_post) !== '') {
            return true;
        }

        return false;
    }

    /**
     * Returns normalized app view key for a plugin app page, otherwise empty string.
     *
     * @param WP_Post|int|null $post
     * @return string
     */
    public function get_app_view($post = null) {
        $resolved_post = $this->resolve_app_page_post($post);
        if (!$resolved_post instanceof WP_Post) {
            return '';
        }

        $meta_view = sanitize_key((string) get_post_meta((int) $resolved_post->ID, '_nexdirectory_app_view', true));
        if ($meta_view !== '') {
            return $meta_view;
        }

        $content_view = $this->extract_app_view_from_content((string) $resolved_post->post_content);
        if ($content_view !== '') {
            return $content_view;
        }

        $platform_page_ids = $this->get_platform_page_ids_for_title_hiding();
        if (in_array((int) $resolved_post->ID, $platform_page_ids, true)) {
            return 'app';
        }

        return '';
    }

    /**
     * Resolves a page post instance for app-page checks.
     *
     * @param WP_Post|int|null $post
     * @return WP_Post|null
     */
    private function resolve_app_page_post($post = null) {
        if (is_admin() || (function_exists('wp_doing_ajax') && wp_doing_ajax())) {
            return null;
        }
        if (defined('REST_REQUEST') && REST_REQUEST) {
            return null;
        }

        if ($post instanceof WP_Post) {
            if ($post->post_type === 'page') {
                return $post;
            }
            return null;
        }

        if (is_numeric($post) && (int) $post > 0) {
            $candidate = get_post((int) $post);
            if ($candidate instanceof WP_Post && $candidate->post_type === 'page') {
                return $candidate;
            }
            return null;
        }

        if (!is_page()) {
            return null;
        }

        $queried = get_queried_object();
        if ($queried instanceof WP_Post && $queried->post_type === 'page') {
            return $queried;
        }

        $queried_id = (int) get_queried_object_id();
        if ($queried_id > 0) {
            $candidate = get_post($queried_id);
            if ($candidate instanceof WP_Post && $candidate->post_type === 'page') {
                return $candidate;
            }
        }

        return null;
    }

    /**
     * Known app shortcode->view mapping.
     *
     * @return array<string, string>
     */
    private function get_app_shortcode_view_map() {
        $map = [
            'nexdirectory_events_archive' => 'events',
            'nexdirectory_listings_archive' => 'events',
            'nexdirectory_events_map' => 'map',
            'nexdirectory_listings_map' => 'map',
            'nexdirectory_events_calendar' => 'calendar',
            'nexdirectory_listings_calendar' => 'calendar',
            'nexdirectory_submit_event' => 'submit',
            'nexdirectory_submit' => 'submit',
            'nexdirectory_my_events' => 'my-events',
            'nexdirectory_my_listings' => 'my-events',
            'nexdirectory_my_tickets' => 'my-tickets',
            'nexdirectory_saved_listings' => 'my-tickets',
            'nexdirectory_whos_attending' => 'whos-attending',
            'nexdirectory_attendees' => 'whos-attending',
            'nexdirectory_notifications' => 'notifications',
            'nexdirectory_whats_new' => 'whats-new',
            'nexdirectory_account' => 'account',
            'nexdirectory_auth' => 'auth',
            'nexdirectory_user_management' => 'user-management',
            'nexdirectory_moderation_queue' => 'moderation-queue',
            'nexdirectory_organizers' => 'organizers',
            'nexdirectory_claim_listing' => 'claim-listing',
        ];

        return (array) apply_filters('nexdirectory_extension_navigation_app_shortcode_view_map', $map);
    }

    /**
     * Extracts app view key from page content shortcodes.
     *
     * @param string $content
     * @return string
     */
    private function extract_app_view_from_content($content) {
        $content = (string) $content;
        if ($content === '') {
            return '';
        }

        $shortcode_view_map = $this->get_app_shortcode_view_map();
        $content = NEXDIRECTORY_Shortcodes::normalize_legacy_content($content, array_keys($shortcode_view_map));

        foreach ($shortcode_view_map as $shortcode_tag => $view) {
            if (has_shortcode($content, $shortcode_tag)) {
                return sanitize_key((string) $view);
            }
        }

        return '';
    }

    /**
     * Checks whether the current frontend request belongs to platform UI pages.
     */
    private function is_platform_frontend_navigation_context() {
        if (is_admin() || (function_exists('wp_doing_ajax') && wp_doing_ajax())) {
            return false;
        }
        if (defined('REST_REQUEST') && REST_REQUEST) {
            return false;
        }
        $platform_taxonomies = $this->get_platform_taxonomy_slugs();
        if (!empty($platform_taxonomies) && is_tax($platform_taxonomies)) {
            return true;
        }
        if (is_category() && in_array('category', $platform_taxonomies, true)) {
            return true;
        }
        if (is_tag() && in_array('post_tag', $platform_taxonomies, true)) {
            return true;
        }
        if (is_singular($this->post_type) || is_post_type_archive($this->post_type)) {
            return true;
        }
        if ($this->is_app_page()) {
            return true;
        }
        return false;
    }

    /**
     * Returns taxonomy slugs registered for the platform post type.
     *
     * @return array<int, string>
     */
    private function get_platform_taxonomy_slugs() {
        static $cached = null;
        if (is_array($cached)) {
            return $cached;
        }

        $taxonomies = get_object_taxonomies($this->post_type, 'names');
        if (!is_array($taxonomies)) {
            $cached = [];
            return $cached;
        }

        $normalized = [];
        foreach ($taxonomies as $taxonomy) {
            $slug = sanitize_key((string) $taxonomy);
            if ($slug === '') {
                continue;
            }
            $normalized[$slug] = $slug;
        }

        $cached = array_values($normalized);
        return $cached;
    }

    /**
     * Returns sticky-nav behavior setting with safe fallback.
     */
    private function get_frontend_sticky_nav_behavior() {
        $behavior = sanitize_key((string) get_option('nexdirectory_frontend_sticky_nav_behavior', 'sticky'));
        if (!in_array($behavior, ['sticky', 'static', 'smart'], true)) {
            $behavior = 'sticky';
        }
        return $behavior;
    }

    /**
     * Resolves operational pages (notifications, moderation, user management) by slug/shortcode.
     *
     * @param array<int, string> $fallback_slugs
     * @param array<int, string> $shortcode_tags
     * @return string
     */
    private function get_frontend_ops_page_url(array $fallback_slugs = [], array $shortcode_tags = []) {
        foreach ($fallback_slugs as $slug) {
            $slug = sanitize_title((string) $slug);
            if ($slug === '') {
                continue;
            }
            $page = get_page_by_path($slug);
            if (!$page instanceof WP_Post || is_wp_error($page)) {
                continue;
            }
            if ((string) get_post_status((int) $page->ID) !== 'publish') {
                continue;
            }
            $url = get_permalink((int) $page->ID);
            if (is_string($url) && $url !== '') {
                return esc_url_raw((string) $url);
            }
        }

        global $wpdb;
        if ($wpdb instanceof wpdb) {
            foreach ($shortcode_tags as $shortcode_tag) {
                $shortcode_tag = sanitize_key((string) $shortcode_tag);
                if ($shortcode_tag === '') {
                    continue;
                }
                $like = '%' . $wpdb->esc_like('[' . $shortcode_tag) . '%';
                $page_id = (int) $wpdb->get_var(
                    $wpdb->prepare(
                        "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'page' AND post_status = 'publish' AND post_content LIKE %s ORDER BY ID ASC LIMIT 1",
                        $like
                    )
                ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                if ($page_id <= 0) {
                    continue;
                }
                $url = get_permalink($page_id);
                if (is_string($url) && $url !== '') {
                    return esc_url_raw((string) $url);
                }
            }
        }

        return '';
    }

    /**
     * Resolves notifications center URL.
     */
    private function get_notifications_page_url() {
        $configured = esc_url_raw((string) get_option('nexdirectory_page_notifications_url', ''));
        if ($configured !== '') {
            return $configured;
        }
        $resolved = $this->get_frontend_ops_page_url(
            ['notifications', 'inbox'],
            ['nexdirectory_notifications', 'nexdirectory_notifications', 'nexdirectory_inbox', 'nexdirectory_inbox']
        );
        if ($resolved !== '') {
            return $resolved;
        }
        return esc_url_raw(home_url('/notifications/'));
    }

    /**
     * Appends a nav link while preventing duplicates.
     *
     * @param array<int, array<string, string>> $bucket
     * @param array<string, bool> $seen
     * @return void
     */
    private function add_unique_nav_link(array &$bucket, array &$seen, $label, $url) {
        $label = sanitize_text_field((string) $label);
        $url = esc_url_raw((string) $url);
        if ($label === '' || $url === '') {
            return;
        }
        if ($this->should_hide_boost_nav_link($label, $url)) {
            return;
        }
        $path = '/' . ltrim((string) wp_parse_url($url, PHP_URL_PATH), '/');
        if ($path !== '/') {
            $path = untrailingslashit($path);
        }
        $query = (string) wp_parse_url($url, PHP_URL_QUERY);
        if ($query !== '') {
            parse_str($query, $query_args);
            if (is_array($query_args)) {
                ksort($query_args);
                $query = http_build_query($query_args);
            }
        }
        $key = strtolower($label . '|' . $path . ($query !== '' ? '?' . $query : ''));
        if (isset($seen[$key])) {
            return;
        }
        $seen[$key] = true;
        $bucket[] = [
            'label' => $label,
            'url' => $url,
        ];
    }

    /**
     * Returns true when a nav link should be hidden from frontend menus.
     *
     * @param string $label
     * @param string $url
     * @return bool
     */
    private function should_hide_boost_nav_link($label, $url) {
        $label_value = strtolower(trim(sanitize_text_field((string) $label)));
        $url_value = esc_url_raw((string) $url);

        $link_path = '/' . ltrim((string) wp_parse_url($url_value, PHP_URL_PATH), '/');
        if ($link_path !== '/') {
            $link_path = untrailingslashit($link_path);
        }
        $link_path_lc = strtolower($link_path);

        if (!is_user_logged_in()) {
            $configured_submit_url = esc_url_raw((string) get_option('nexdirectory_page_submit_event_url', ''));
            $configured_submit_path = '';
            if ($configured_submit_url !== '') {
                $configured_submit_path = '/' . ltrim((string) wp_parse_url($configured_submit_url, PHP_URL_PATH), '/');
                if ($configured_submit_path !== '/') {
                    $configured_submit_path = untrailingslashit($configured_submit_path);
                }
                $configured_submit_path = strtolower($configured_submit_path);
            }
            $is_submit_link = (
                strpos($label_value, 'add new') !== false
                || strpos($label_value, 'submit') !== false
                || ($configured_submit_path !== '' && $configured_submit_path !== '/' && $configured_submit_path === $link_path_lc)
                || strpos($link_path_lc, '/submit') !== false
                || strpos($link_path_lc, '/add-new-event') !== false
            );
            if ($is_submit_link) {
                return true;
            }
        }

        if ($label_value !== '' && strpos($label_value, 'boost') !== false) {
            return true;
        }

        $configured_boost_url = esc_url_raw((string) get_option('nexdirectory_page_boost_posts_url', ''));
        if ($configured_boost_url !== '') {
            $configured_path = '/' . ltrim((string) wp_parse_url($configured_boost_url, PHP_URL_PATH), '/');
            if ($configured_path !== '/') {
                $configured_path = untrailingslashit($configured_path);
            }
            if ($configured_path !== '/' && strtolower($configured_path) === $link_path_lc) {
                return true;
            }
        }

        if ($link_path_lc !== '/' && (strpos($link_path_lc, '/boost') !== false || strpos($link_path_lc, '/boost-post') !== false)) {
            return true;
        }

        return false;
    }

    /**
     * Removes blocked links (e.g., Boost) from a prepared nav menu group structure.
     *
     * @param array<int, array<string, mixed>> $groups
     * @return array<int, array<string, mixed>>
     */
    private function filter_boost_links_from_menu_groups(array $groups) {
        $filtered_groups = [];

        foreach ($groups as $group) {
            if (!is_array($group)) {
                continue;
            }

            $group_label = sanitize_text_field((string) ($group['label'] ?? ''));
            if ($group_label !== '' && strpos(strtolower($group_label), 'boost') !== false) {
                continue;
            }

            $group_links = isset($group['links']) && is_array($group['links']) ? $group['links'] : [];
            $filtered_links = [];
            foreach ($group_links as $link) {
                if (!is_array($link)) {
                    continue;
                }
                $link_label = sanitize_text_field((string) ($link['label'] ?? ''));
                $link_url = esc_url_raw((string) ($link['url'] ?? ''));
                if ($link_label === '' || $link_url === '') {
                    continue;
                }
                if ($this->should_hide_boost_nav_link($link_label, $link_url)) {
                    continue;
                }
                $filtered_links[] = [
                    'label' => $link_label,
                    'url' => $link_url,
                ];
            }

            if (!empty($filtered_links)) {
                $filtered_groups[] = [
                    'label' => $group_label,
                    'links' => $filtered_links,
                ];
            }
        }

        return $filtered_groups;
    }

    /**
     * Builds grouped sticky-nav menu links from routed pages and custom settings.
     *
     * @return array<int, array<string, mixed>>
     */
    private function get_frontend_sticky_nav_menu_groups() {
        $wp_menu_id = $this->get_frontend_sticky_nav_wp_menu_id_for_current_user();
        if ($wp_menu_id > 0) {
            $wp_menu_groups = $this->get_frontend_sticky_nav_menu_groups_from_wp_menu($wp_menu_id);
            if (!empty($wp_menu_groups)) {
                return $wp_menu_groups;
            }
        }

        $is_member = is_user_logged_in();
        $can_manage = current_user_can('manage_options');
        $can_moderate = $can_manage || current_user_can('edit_others_posts');
        $can_manage_users = $can_manage || current_user_can('list_users');
        $platform_mode = sanitize_key((string) get_option('nexdirectory_platform_mode', 'user_generated'));
        $single_org_non_admin = $platform_mode === 'single_organisation' && !$can_manage;
        $can_submit_from_nav = !$single_org_non_admin;

        $seen = [];
        $explore_links = [];
        $workspace_links = [];
        $publish_links = [];
        $admin_links = [];

        $this->add_unique_nav_link(
            $explore_links,
            $seen,
            __('Explore', 'nexdirectory'),
            $this->get_platform_page_url('nexdirectory_page_events_url', ['events', 'listings'], home_url('/events/'))
        );
        $this->add_unique_nav_link(
            $explore_links,
            $seen,
            __('Map', 'nexdirectory'),
            $this->get_platform_page_url('nexdirectory_page_events_map_url', ['map', 'events-map'], home_url('/map/'))
        );
        $this->add_unique_nav_link(
            $explore_links,
            $seen,
            __('Calendar', 'nexdirectory'),
            $this->get_platform_page_url('nexdirectory_page_events_calendar_url', ['calendar', 'events-calendar'], home_url('/calendar/'))
        );
        $this->add_unique_nav_link(
            $explore_links,
            $seen,
            __('What\'s New', 'nexdirectory'),
            $this->get_platform_page_url('nexdirectory_page_whats_new_url', ['whats-new'], home_url('/whats-new/'))
        );

        if ($is_member && $can_submit_from_nav) {
            $this->add_unique_nav_link(
                $publish_links,
                $seen,
                __('Add New Event', 'nexdirectory'),
                $this->get_platform_page_url('nexdirectory_page_submit_event_url', ['submit', 'add-new-event'], home_url('/submit/'))
            );
        }

        if ($is_member) {
            if ($single_org_non_admin) {
                $this->add_unique_nav_link(
                    $workspace_links,
                    $seen,
                    __('Dashboard', 'nexdirectory'),
                    $this->get_platform_page_url('nexdirectory_page_account_url', ['account', 'my-account'], home_url('/account/'))
                );
            } else {
                $this->add_unique_nav_link(
                    $workspace_links,
                    $seen,
                    __('Workspace', 'nexdirectory'),
                    $this->get_platform_page_url('nexdirectory_page_my_events_url', ['my-events', 'workspace'], home_url('/my-events/'))
                );
            }
            $this->add_unique_nav_link(
                $workspace_links,
                $seen,
                __('Saved & Attending', 'nexdirectory'),
                $this->get_platform_page_url('nexdirectory_page_my_tickets_url', ['saved', 'attending', 'my-tickets'], home_url('/my-tickets/'))
            );
            $this->add_unique_nav_link(
                $workspace_links,
                $seen,
                __('Messages', 'nexdirectory'),
                $this->get_notifications_page_url()
            );
            $this->add_unique_nav_link(
                $workspace_links,
                $seen,
                __('Account Settings', 'nexdirectory'),
                $this->get_platform_page_url('nexdirectory_page_account_url', ['account', 'my-account'], home_url('/account/'))
            );
            $account_analytics_url = nexdirectory_add_query_arg(
                'nexdirectory_tab',
                'analytics',
                $this->get_platform_page_url('nexdirectory_page_account_url', ['account', 'my-account'], home_url('/account/'))
            ) . '#analytics';
            $this->add_unique_nav_link($workspace_links, $seen, __('Analytics', 'nexdirectory'), $account_analytics_url);
        }

        if ($can_moderate) {
            $moderation_url = $this->get_frontend_ops_page_url(
                ['moderation-queue'],
                ['nexdirectory_moderation_queue', 'nexdirectory_moderation_queue']
            );
            if ($moderation_url === '' && $can_manage) {
                $moderation_url = esc_url_raw(admin_url('admin.php?page=nexdirectory-moderation'));
            }
            $this->add_unique_nav_link($admin_links, $seen, __('Moderation Queue', 'nexdirectory'), $moderation_url);
        }
        if ($can_manage_users) {
            $user_management_url = $this->get_frontend_ops_page_url(
                ['user-management'],
                ['nexdirectory_user_management', 'nexdirectory_user_management']
            );
            if ($user_management_url === '' && $can_manage) {
                $user_management_url = esc_url_raw(admin_url('users.php'));
            }
            $this->add_unique_nav_link($admin_links, $seen, __('User Management', 'nexdirectory'), $user_management_url);
            $this->add_unique_nav_link(
                $admin_links,
                $seen,
                __('Organizers', 'nexdirectory'),
                $this->get_frontend_ops_page_url(['organizers'], ['nexdirectory_organizers', 'nexdirectory_organizers'])
            );
        }
        if ($can_manage) {
            $this->add_unique_nav_link($admin_links, $seen, __('Platform Settings', 'nexdirectory'), admin_url('admin.php?page=nexdirectory-settings'));
        }

        $custom_group_links = [];
        foreach ($this->parse_frontend_sticky_nav_custom_links() as $custom_link) {
            $audience = sanitize_key((string) ($custom_link['audience'] ?? 'all'));
            if ($audience === 'member' && !$is_member) {
                continue;
            }
            if ($audience === 'guest' && $is_member) {
                continue;
            }
            $group = sanitize_text_field((string) ($custom_link['group'] ?? __('More', 'nexdirectory')));
            if ($group === '') {
                $group = __('More', 'nexdirectory');
            }
            if (!isset($custom_group_links[$group]) || !is_array($custom_group_links[$group])) {
                $custom_group_links[$group] = [];
            }
            $this->add_unique_nav_link(
                $custom_group_links[$group],
                $seen,
                (string) ($custom_link['label'] ?? ''),
                (string) ($custom_link['url'] ?? '')
            );
        }

        $groups = [];
        if (!empty($explore_links)) {
            $groups[] = ['label' => __('Explore', 'nexdirectory'), 'links' => array_values($explore_links)];
        }
        if ($is_member && !empty($workspace_links)) {
            $groups[] = ['label' => __('Workspace', 'nexdirectory'), 'links' => array_values($workspace_links)];
        }
        if (!empty($publish_links)) {
            $groups[] = ['label' => $is_member ? __('Publish', 'nexdirectory') : __('Get Started', 'nexdirectory'), 'links' => array_values($publish_links)];
        }
        if (!empty($admin_links)) {
            $groups[] = ['label' => __('Admin', 'nexdirectory'), 'links' => array_values($admin_links)];
        }
        foreach ($custom_group_links as $group_label => $group_links) {
            if (empty($group_links)) {
                continue;
            }
            $groups[] = ['label' => sanitize_text_field((string) $group_label), 'links' => array_values($group_links)];
        }

        return $groups;
    }

    /**
     * Evaluates whether the platform footer should be shown on the current request.
     *
     * @return bool
     */
    public function should_show_frontend_footer_for_current_user() {
        if (!$this->is_option_enabled('nexdirectory_enable_frontend_footer', '1')) {
            return false;
        }
        if (!$this->is_platform_frontend_navigation_context()) {
            return false;
        }
        return true;
    }

    /**
     * Returns normalized footer desktop menu column count (1..5).
     *
     * @return int
     */
    private function get_frontend_footer_menu_count() {
        $count = absint((int) get_option('nexdirectory_frontend_footer_menu_count', 3));
        if ($count < 1) {
            $count = 1;
        } elseif ($count > 5) {
            $count = 5;
        }
        return $count;
    }

    /**
     * Returns selected footer WP menu IDs (deduped, max 5).
     *
     * @return array<int, int>
     */
    private function get_frontend_footer_wp_menu_ids() {
        $raw_ids = get_option('nexdirectory_frontend_footer_wp_menus', []);
        if (!is_array($raw_ids)) {
            return [];
        }

        $menu_ids = [];
        foreach ($raw_ids as $raw_id) {
            $menu_id = absint((int) $raw_id);
            if ($menu_id <= 0) {
                continue;
            }
            if (isset($menu_ids[$menu_id])) {
                continue;
            }
            $menu_obj = wp_get_nav_menu_object($menu_id);
            if (!$menu_obj instanceof WP_Term) {
                continue;
            }
            $menu_ids[$menu_id] = $menu_id;
            if (count($menu_ids) >= 5) {
                break;
            }
        }

        return array_values($menu_ids);
    }

    /**
     * Builds a flat footer link list from one WP menu.
     *
     * @param int $menu_id
     * @param int $max_links
     * @return array<int, array<string, string>>
     */
    private function get_frontend_footer_links_from_wp_menu($menu_id, $max_links = 16) {
        $menu_id = absint((int) $menu_id);
        if ($menu_id <= 0) {
            return [];
        }

        $menu_items = wp_get_nav_menu_items($menu_id, ['update_post_term_cache' => false]);
        if (!is_array($menu_items) || empty($menu_items)) {
            return [];
        }

        $indexed = [];
        foreach ($menu_items as $menu_item) {
            if (!is_object($menu_item)) {
                continue;
            }
            $item_id = absint((int) ($menu_item->ID ?? 0));
            if ($item_id <= 0) {
                continue;
            }
            $label = sanitize_text_field((string) wp_strip_all_tags((string) ($menu_item->title ?? '')));
            if ($label === '') {
                continue;
            }
            $url = $this->normalize_frontend_sticky_nav_wp_menu_url((string) ($menu_item->url ?? ''));
            if ($url === '' || $url === '#') {
                continue;
            }
            $indexed[$item_id] = [
                'id' => $item_id,
                'label' => $label,
                'url' => $url,
                'parent_id' => absint((int) ($menu_item->menu_item_parent ?? 0)),
                'menu_order' => (int) ($menu_item->menu_order ?? 0),
            ];
        }

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

        uasort($indexed, static function ($a, $b) {
            $a_order = (int) ($a['menu_order'] ?? 0);
            $b_order = (int) ($b['menu_order'] ?? 0);
            if ($a_order === $b_order) {
                return ((int) ($a['id'] ?? 0)) <=> ((int) ($b['id'] ?? 0));
            }
            return $a_order <=> $b_order;
        });

        $links = [];
        $seen = [];
        foreach ($indexed as $item) {
            $label = sanitize_text_field((string) ($item['label'] ?? ''));
            $url = esc_url_raw((string) ($item['url'] ?? ''));
            if ($label === '' || $url === '') {
                continue;
            }
            $path = '/' . ltrim((string) wp_parse_url($url, PHP_URL_PATH), '/');
            if ($path !== '/') {
                $path = untrailingslashit($path);
            }
            $query = (string) wp_parse_url($url, PHP_URL_QUERY);
            $dedupe_key = strtolower($label . '|' . $path . ($query !== '' ? ('?' . $query) : ''));
            if (isset($seen[$dedupe_key])) {
                continue;
            }
            $seen[$dedupe_key] = true;
            $links[] = [
                'label' => $label,
                'url' => $url,
            ];
            if (count($links) >= $max_links) {
                break;
            }
        }

        return $links;
    }

    /**
     * Builds footer columns from selected WP menus.
     *
     * @param array<int, int> $menu_ids
     * @param int $max_columns
     * @return array<int, array<string, mixed>>
     */
    private function get_frontend_footer_columns_from_wp_menus(array $menu_ids, $max_columns = 3) {
        $max_columns = max(1, min(5, absint((int) $max_columns)));
        if (empty($menu_ids)) {
            return [];
        }

        $columns = [];
        foreach ($menu_ids as $menu_id_raw) {
            $menu_id = absint((int) $menu_id_raw);
            if ($menu_id <= 0) {
                continue;
            }
            $menu_obj = wp_get_nav_menu_object($menu_id);
            if (!$menu_obj instanceof WP_Term) {
                continue;
            }
            $links = $this->get_frontend_footer_links_from_wp_menu($menu_id, 16);
            if (empty($links)) {
                continue;
            }
            $columns[] = [
                'label' => sanitize_text_field((string) $menu_obj->name),
                'links' => $links,
            ];
            if (count($columns) >= $max_columns) {
                break;
            }
        }

        return $columns;
    }

    /**
     * Resolves footer columns from WP menu source or auto-generated nav groups.
     *
     * @return array<int, array<string, mixed>>
     */
    private function get_frontend_footer_columns() {
        $menu_count = $this->get_frontend_footer_menu_count();
        $columns = [];

        if ($this->is_option_enabled('nexdirectory_frontend_footer_use_wp_menus', '0')) {
            $columns = $this->get_frontend_footer_columns_from_wp_menus(
                $this->get_frontend_footer_wp_menu_ids(),
                $menu_count
            );
        }

        $menu_groups = $this->get_frontend_sticky_nav_menu_groups();
        if (!empty($menu_groups) && count($columns) < $menu_count) {
            $existing_labels = [];
            foreach ($columns as $column) {
                $existing_label = sanitize_key((string) ($column['label'] ?? ''));
                if ($existing_label !== '') {
                    $existing_labels[$existing_label] = true;
                }
            }

            foreach ($menu_groups as $group) {
                if (!is_array($group)) {
                    continue;
                }
                $label = sanitize_text_field((string) ($group['label'] ?? ''));
                $label_key = sanitize_key($label);
                $links_raw = isset($group['links']) && is_array($group['links']) ? $group['links'] : [];
                if ($label === '' || $label_key === '' || isset($existing_labels[$label_key]) || empty($links_raw)) {
                    continue;
                }
                $links = [];
                foreach ($links_raw as $link) {
                    if (!is_array($link)) {
                        continue;
                    }
                    $link_label = sanitize_text_field((string) ($link['label'] ?? ''));
                    $link_url = esc_url_raw((string) ($link['url'] ?? ''));
                    if ($link_label === '' || $link_url === '') {
                        continue;
                    }
                    $links[] = [
                        'label' => $link_label,
                        'url' => $link_url,
                    ];
                    if (count($links) >= 12) {
                        break;
                    }
                }
                if (empty($links)) {
                    continue;
                }
                $columns[] = [
                    'label' => $label,
                    'links' => $links,
                ];
                $existing_labels[$label_key] = true;
                if (count($columns) >= $menu_count) {
                    break;
                }
            }
        }

        if (empty($columns)) {
            $columns[] = [
                'label' => __('Explore', 'nexdirectory'),
                'links' => [
                    [
                        'label' => __('Events', 'nexdirectory'),
                        'url' => $this->get_platform_page_url('nexdirectory_page_events_url', ['events', 'listings'], home_url('/events/')),
                    ],
                    [
                        'label' => __('Map', 'nexdirectory'),
                        'url' => $this->get_platform_page_url('nexdirectory_page_events_map_url', ['events-map', 'map'], home_url('/events-map/')),
                    ],
                    [
                        'label' => __('Calendar', 'nexdirectory'),
                        'url' => $this->get_platform_page_url('nexdirectory_page_events_calendar_url', ['events-calendar', 'calendar'], home_url('/events-calendar/')),
                    ],
                ],
            ];
        }

        return array_slice($columns, 0, $menu_count);
    }

    /**
     * Renders the frontend platform footer.
     *
     * @return string
     */
    public function render_frontend_platform_footer() {
        if (!$this->should_show_frontend_footer_for_current_user()) {
            return '';
        }

        $show_logo = $this->is_option_enabled('nexdirectory_frontend_footer_show_logo', '1');
        $show_privacy_link = $this->is_option_enabled('nexdirectory_frontend_footer_show_privacy_link', '1');
        $brand_name = $this->get_platform_brand_name();
        $logo_url = esc_url((string) get_option('nexdirectory_frontend_footer_logo_url', ''));
        if ($logo_url === '') {
            $logo_url = esc_url((string) get_option('nexdirectory_frontend_sticky_nav_logo_url', ''));
        }
        $logo_link_url = esc_url((string) get_option('nexdirectory_frontend_footer_logo_link_url', home_url('/')));
        if ($logo_link_url === '') {
            $logo_link_url = esc_url(home_url('/'));
        }
        $bottom_text = sanitize_text_field((string) get_option('nexdirectory_frontend_footer_bottom_text', ''));
        $columns = $this->get_frontend_footer_columns();
        $column_count = count($columns);
        if ($column_count < 1) {
            $column_count = 1;
        }

        $events_url = $this->get_platform_page_url('nexdirectory_page_events_url', ['events', 'listings'], home_url('/events/'));
        $privacy_url = function_exists('get_privacy_policy_url') ? esc_url_raw((string) get_privacy_policy_url()) : '';
        $year_label = wp_date('Y');
        if ($year_label === '') {
            $year_label = gmdate('Y');
        }

        ob_start();
        ?>
        <footer class="nexdirectory-platform-footer w-full border-t border-gray-200/70 bg-white/95 backdrop-blur supports-[backdrop-filter]:bg-white/90 mt-8">
            <div class="nexdirectory-platform-footer-inner mx-auto w-full py-10 md:py-12">
                <div class="grid gap-8 lg:grid-cols-[minmax(240px,1.2fr)_minmax(0,1fr)] items-start">
                    <?php if ($show_logo) : ?>
                        <section class="space-y-4">
                            <a href="<?php echo esc_url($logo_link_url); ?>" class="inline-flex items-center max-w-full">
                                <?php if ($logo_url !== '') : ?>
                                    <img src="<?php echo esc_url($logo_url); ?>" alt="<?php echo esc_attr($brand_name); ?>" loading="lazy" decoding="async" class="h-10 w-auto max-w-[220px] object-contain">
                                <?php else : ?>
                                    <span class="text-2xl font-black tracking-tight text-gray-900"><?php echo esc_html($brand_name); ?></span>
                                <?php endif; ?>
                            </a>
                            <p class="text-sm text-gray-500 leading-relaxed">
                                <?php
                                echo esc_html(
                                    sprintf(
                                        /* translators: %s: Placeholder value. */
                                        __('Discover, manage, and publish %s with confidence.', 'nexdirectory'),
                                        strtolower($this->post_type === 'nexdirectory_event' ? __('events', 'nexdirectory') : __('listings', 'nexdirectory'))
                                    )
                                );
                                ?>
                            </p>
                        </section>
                    <?php endif; ?>

                    <div class="nexdirectory-platform-footer-menus" style="--nexdirectory-footer-menu-count: <?php echo esc_attr((string) $column_count); ?>;">
                        <?php foreach ($columns as $column) : ?>
                            <?php
                            $column_label = sanitize_text_field((string) ($column['label'] ?? ''));
                            $column_links = isset($column['links']) && is_array($column['links']) ? $column['links'] : [];
                            if ($column_label === '' || empty($column_links)) {
                                continue;
                            }
                            ?>
                            <nav aria-label="<?php echo esc_attr($column_label); ?>">
                                <h3 class="nexdirectory-footer-col-title"><?php echo esc_html($column_label); ?></h3>
                                <ul class="space-y-2.5 list-none m-0 p-0">
                                    <?php foreach ($column_links as $column_link) : ?>
                                        <?php
                                        $link_label = sanitize_text_field((string) ($column_link['label'] ?? ''));
                                        $link_url = esc_url((string) ($column_link['url'] ?? ''));
                                        if ($link_label === '' || $link_url === '') {
                                            continue;
                                        }
                                        ?>
                                        <li>
                                            <a href="<?php echo esc_url($link_url); ?>" class="nexdirectory-footer-link">
                                                <?php echo esc_html($link_label); ?>
                                            </a>
                                        </li>
                                    <?php endforeach; ?>
                                </ul>
                            </nav>
                        <?php endforeach; ?>
                    </div>
                </div>

                <div class="mt-8 pt-5 border-t border-gray-200/70 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
                    <p class="text-xs sm:text-sm text-gray-500 m-0">
                        &copy; <?php echo esc_html((string) $year_label); ?> <?php echo esc_html($brand_name); ?>.
                        <?php if ($bottom_text !== '') : ?>
                            <span><?php echo esc_html($bottom_text); ?></span>
                        <?php endif; ?>
                    </p>
                    <div class="flex flex-wrap items-center gap-3 text-xs sm:text-sm">
                        <?php if ($show_privacy_link && $privacy_url !== '') : ?>
                            <a href="<?php echo esc_url($privacy_url); ?>" class="nexdirectory-footer-legal-link"><?php esc_html_e('Privacy Policy', 'nexdirectory'); ?></a>
                        <?php endif; ?>
                        <a href="<?php echo esc_url($events_url); ?>" class="nexdirectory-footer-legal-link"><?php esc_html_e('Explore Events', 'nexdirectory'); ?></a>
                    </div>
                </div>
            </div>
        </footer>
        <?php

        return (string) ob_get_clean();
    }

    /**
     * Detects unusable nav menu output (e.g. only one home/root URL link).
     *
     * @param array<int, array<string, mixed>> $menu_groups
     * @return bool
     */
    private function frontend_nav_menu_groups_need_fallback($menu_groups) {
        if (!is_array($menu_groups) || empty($menu_groups)) {
            return true;
        }

        $total_links = 0;
        $non_root_links = 0;
        foreach ($menu_groups as $group) {
            if (!is_array($group)) {
                continue;
            }
            $group_links = isset($group['links']) && is_array($group['links']) ? $group['links'] : [];
            foreach ($group_links as $group_link) {
                if (!is_array($group_link)) {
                    continue;
                }
                $url = esc_url_raw((string) ($group_link['url'] ?? ''));
                if ($url === '') {
                    continue;
                }
                $total_links++;
                if (!$this->is_site_root_url($url)) {
                    $non_root_links++;
                }
            }
        }

        if ($total_links === 0) {
            return true;
        }
        if ($total_links <= 1) {
            return true;
        }
        return $non_root_links === 0;
    }

    /**
     * Renders the frontend sticky navigation bar.
     *
     * @return string
     */
    public function render_frontend_sticky_nav() {
        if (!$this->should_show_frontend_sticky_nav_for_current_user()) {
            return '';
        }
        if (!$this->is_platform_frontend_navigation_context()) {
            return '';
        }

        $behavior = $this->get_frontend_sticky_nav_behavior();
        $show_logo = $this->is_option_enabled('nexdirectory_frontend_sticky_nav_show_logo', '1');
        $include_auth_cta = $this->is_option_enabled('nexdirectory_frontend_sticky_nav_include_auth_cta', '1');
        $logo_url = esc_url((string) get_option('nexdirectory_frontend_sticky_nav_logo_url', ''));
        $logo_link_url = esc_url((string) get_option('nexdirectory_frontend_sticky_nav_logo_link_url', home_url('/')));
        $logo_size_raw = strtolower(trim((string) get_option('nexdirectory_frontend_sticky_nav_logo_size', '66px')));
        $logo_size_value = '66px';
        if (preg_match('/^(\d+(?:\.\d+)?)(px|rem|em)?$/', $logo_size_raw, $logo_size_matches)) {
            $logo_size_amount = (float) $logo_size_matches[1];
            if ($logo_size_amount < 18) {
                $logo_size_amount = 18;
            }
            if ($logo_size_amount > 96) {
                $logo_size_amount = 96;
            }
            $logo_size_unit = (isset($logo_size_matches[2]) && $logo_size_matches[2] !== '') ? (string) $logo_size_matches[2] : 'px';
            $logo_size_value = rtrim(rtrim(number_format($logo_size_amount, 2, '.', ''), '0'), '.') . $logo_size_unit;
        }
        $logo_max_width_value = '190px';
        if (preg_match('/^(\d+(?:\.\d+)?)(px|rem|em)$/', $logo_size_value, $logo_size_parsed)) {
            $logo_max_multiplier = 5.3;
            $logo_max_width_value = 'calc(' . $logo_size_parsed[1] . $logo_size_parsed[2] . ' * ' . $logo_max_multiplier . ')';
        }
        if ($logo_link_url === '') {
            $logo_link_url = esc_url(home_url('/'));
        }

        $menu_groups = $this->get_frontend_sticky_nav_menu_groups();
        $prepared_groups = [];
        foreach ((array) $menu_groups as $group) {
            $group_label = sanitize_text_field((string) ($group['label'] ?? ''));
            $group_links_raw = isset($group['links']) && is_array($group['links']) ? $group['links'] : [];
            $group_links = [];
            foreach ($group_links_raw as $group_link) {
                $link_label = sanitize_text_field((string) ($group_link['label'] ?? ''));
                $link_url = esc_url_raw((string) ($group_link['url'] ?? ''));
                if ($link_label === '' || $link_url === '') {
                    continue;
                }
                $group_links[] = [
                    'label' => $link_label,
                    'url' => $link_url,
                ];
            }
            if ($group_label === '' || empty($group_links)) {
                continue;
            }
            $prepared_groups[] = [
                'label' => $group_label,
                'links' => $group_links,
            ];
        }
        $menu_groups = $prepared_groups;
        if ($this->frontend_nav_menu_groups_need_fallback($menu_groups)) {
            $fallback_links = [];
            $fallback_events_url = $this->get_platform_page_url('nexdirectory_page_events_url', ['events', 'listings'], home_url('/events/'));
            $fallback_map_url = $this->get_platform_page_url('nexdirectory_page_events_map_url', ['map', 'events-map'], home_url('/map/'));
            $fallback_calendar_url = $this->get_platform_page_url('nexdirectory_page_events_calendar_url', ['calendar', 'events-calendar'], home_url('/calendar/'));
            $fallback_submit_url = $this->get_platform_page_url('nexdirectory_page_submit_event_url', ['submit', 'add-new-event'], home_url('/submit/'));
            $platform_mode = sanitize_key((string) get_option('nexdirectory_platform_mode', 'user_generated'));
            $allow_submit_link = is_user_logged_in() && !($platform_mode === 'single_organisation' && !current_user_can('manage_options'));

            $fallback_map = [
                ['label' => __('Explore', 'nexdirectory'), 'url' => $fallback_events_url],
                ['label' => __('Map', 'nexdirectory'), 'url' => $fallback_map_url],
                ['label' => __('Calendar', 'nexdirectory'), 'url' => $fallback_calendar_url],
            ];
            if ($allow_submit_link) {
                $fallback_map[] = ['label' => __('Add New Event', 'nexdirectory'), 'url' => $fallback_submit_url];
            }

            foreach ($fallback_map as $fallback_link) {
                $link_label = sanitize_text_field((string) ($fallback_link['label'] ?? ''));
                $link_url = esc_url_raw((string) ($fallback_link['url'] ?? ''));
                if ($link_label === '' || $link_url === '' || $this->is_site_root_url($link_url)) {
                    continue;
                }
                $fallback_links[] = [
                    'label' => $link_label,
                    'url' => $link_url,
                ];
            }
            if (!empty($fallback_links)) {
                $menu_groups[] = [
                    'label' => __('Menu', 'nexdirectory'),
                    'links' => $fallback_links,
                ];
            }
        }

        // Final safety fallback: never render an empty mobile/desktop nav.
        if (empty($menu_groups)) {
            $safe_links = [];
            $safe_links[] = [
                'label' => __('Explore', 'nexdirectory'),
                'url' => esc_url_raw(home_url('/events/')),
            ];
            $safe_links[] = [
                'label' => __('Map', 'nexdirectory'),
                'url' => esc_url_raw(home_url('/events-map/')),
            ];
            $safe_links[] = [
                'label' => __('Calendar', 'nexdirectory'),
                'url' => esc_url_raw(home_url('/events-calendar/')),
            ];
            if (is_user_logged_in()) {
                $safe_links[] = [
                    'label' => __('Workspace', 'nexdirectory'),
                    'url' => esc_url_raw(home_url('/my-events/')),
                ];
            }
            $platform_mode = sanitize_key((string) get_option('nexdirectory_platform_mode', 'user_generated'));
            if (is_user_logged_in() && !($platform_mode === 'single_organisation' && !current_user_can('manage_options'))) {
                $safe_links[] = [
                    'label' => __('Add New Event', 'nexdirectory'),
                    'url' => esc_url_raw(home_url('/submit/')),
                ];
            }
            $menu_groups[] = [
                'label' => __('Menu', 'nexdirectory'),
                'links' => $safe_links,
            ];
        }

        $brand_name = $this->get_platform_brand_name();

        $current_url = (string) $this->current_url();
        $login_base = $this->get_custom_login_page_url();
        if ($login_base === '') {
            $login_url = wp_login_url($current_url);
            $signup_url = nexdirectory_add_query_arg('redirect_to', $current_url, wp_registration_url());
        } else {
            $login_url = nexdirectory_add_query_arg('redirect_to', $current_url, $login_base);
            $signup_url = nexdirectory_add_query_arg('nexdirectory_auth', 'signup', $login_url);
        }
        $account_url = $this->get_platform_page_url('nexdirectory_page_account_url', ['account', 'my-account'], home_url('/account/'));
        $notifications_url = $this->get_notifications_page_url();
        $logout_url = wp_logout_url($login_url);
        $is_member = is_user_logged_in();

        if (empty($menu_groups) && !$include_auth_cta && !$show_logo) {
            return '';
        }

        $position_class = $behavior === 'static' ? 'relative' : 'sticky top-0';
        static $nav_script_printed = false;
        if (!$nav_script_printed) {
            $this->render_inline_script_asset(
                'platform-nav-script',
                '(function(){'
                . 'if(window.__nexdirectoryPlatformNavInit){return;}window.__nexdirectoryPlatformNavInit=true;'
                . 'var DESKTOP_BREAKPOINT=1024;'
                . 'function isDesktop(){return window.innerWidth>=DESKTOP_BREAKPOINT;}'
                . 'function initRoot(root){if(!root||root.__nexdirectoryNavBound){return;}root.__nexdirectoryNavBound=true;'
                . 'var behavior=(root.getAttribute("data-nexdirectory-nav-behavior")||"sticky").toLowerCase();'
                . 'var mobileToggle=root.querySelector("[data-nexdirectory-nav-toggle]");'
                . 'var mobileShell=root.querySelector("[data-nexdirectory-nav-mobile-shell]");'
                . 'var mobilePanel=root.querySelector("[data-nexdirectory-nav-mobile-panel]");'
                . 'var openIcon=root.querySelector("[data-nexdirectory-nav-icon-open]");'
                . 'var closeIcon=root.querySelector("[data-nexdirectory-nav-icon-close]");'
                . 'var mobileOpen=false;var mobileCloseTimer=null;'
                . 'function syncIcons(){if(!openIcon||!closeIcon){return;}openIcon.classList.toggle("hidden",mobileOpen);closeIcon.classList.toggle("hidden",!mobileOpen);}'
                . 'function setMobileOpen(next){next=!!next;if(!mobileShell||!mobilePanel){mobileOpen=false;syncIcons();return;}'
                . 'window.clearTimeout(mobileCloseTimer);mobileOpen=next;'
                . 'if(next){mobileShell.classList.remove("hidden");requestAnimationFrame(function(){mobileShell.classList.remove("opacity-0","pointer-events-none");mobileShell.classList.add("opacity-100","pointer-events-auto");mobilePanel.classList.remove("translate-x-full","opacity-0");mobilePanel.classList.add("translate-x-0","opacity-100");window.setTimeout(function(){root.querySelectorAll("[data-nexdirectory-nav-mobile-group]").forEach(function(group){var trigger=group.querySelector("[data-nexdirectory-nav-mobile-group-toggle]");var panel=group.querySelector("[data-nexdirectory-nav-mobile-group-panel]");if(!trigger||!panel||trigger.getAttribute("aria-expanded")!=="true"){return;}panel.classList.remove("hidden");panel.style.maxHeight=panel.scrollHeight+"px";panel.style.opacity="1";});},40);});document.documentElement.classList.add("overflow-hidden");}'
                . 'else{mobileShell.classList.add("opacity-0","pointer-events-none");mobileShell.classList.remove("opacity-100","pointer-events-auto");mobilePanel.classList.add("translate-x-full","opacity-0");mobilePanel.classList.remove("translate-x-0","opacity-100");document.documentElement.classList.remove("overflow-hidden");mobileCloseTimer=window.setTimeout(function(){if(!mobileOpen){mobileShell.classList.add("hidden");}},340);}'
                . 'if(mobileToggle){mobileToggle.setAttribute("aria-expanded",next?"true":"false");}syncIcons();}'
                . 'if(mobileToggle){mobileToggle.addEventListener("click",function(e){e.preventDefault();setMobileOpen(!mobileOpen);});}'
                . 'root.querySelectorAll("[data-nexdirectory-nav-close]").forEach(function(btn){btn.addEventListener("click",function(e){e.preventDefault();setMobileOpen(false);});});'
                . 'root.querySelectorAll("[data-nexdirectory-nav-close-link]").forEach(function(link){link.addEventListener("click",function(){setMobileOpen(false);});});'
                . 'var mobileGroups=Array.prototype.slice.call(root.querySelectorAll("[data-nexdirectory-nav-mobile-group]"));'
                . 'function setMobileGroupState(group,nextExpanded,closeOthers){if(!group){return;}var trigger=group.querySelector("[data-nexdirectory-nav-mobile-group-toggle]");var panel=group.querySelector("[data-nexdirectory-nav-mobile-group-panel]");if(!trigger||!panel){return;}var expanded=!!nextExpanded;if(closeOthers&&expanded){mobileGroups.forEach(function(other){if(other!==group){setMobileGroupState(other,false,false);}});}trigger.setAttribute("aria-expanded",expanded?"true":"false");var chev=trigger.querySelector("[data-nexdirectory-nav-mobile-group-chevron]");if(chev){chev.classList.toggle("rotate-180",expanded);}group.classList.toggle("is-open",expanded);panel.classList.remove("hidden");panel.style.overflow="hidden";panel.style.willChange="max-height,opacity";if(expanded){panel.style.maxHeight=panel.scrollHeight+"px";panel.style.opacity="1";window.setTimeout(function(){if(trigger.getAttribute("aria-expanded")==="true"){panel.style.maxHeight=panel.scrollHeight+"px";}},40);}else{panel.style.maxHeight="0px";panel.style.opacity="0";window.setTimeout(function(){if(trigger.getAttribute("aria-expanded")!=="true"){panel.classList.add("hidden");}},360);}}'
                . 'var firstOpenSet=false;mobileGroups.forEach(function(group){var trigger=group.querySelector("[data-nexdirectory-nav-mobile-group-toggle]");if(!trigger){return;}var expanded=trigger.getAttribute("aria-expanded")==="true";if(expanded){if(firstOpenSet){expanded=false;}else{firstOpenSet=true;}}setMobileGroupState(group,expanded,false);trigger.addEventListener("click",function(){var isOpen=trigger.getAttribute("aria-expanded")==="true";setMobileGroupState(group,!isOpen,true);});});'
                . 'if(!firstOpenSet&&mobileGroups.length){setMobileGroupState(mobileGroups[0],true,false);}'
                . 'var desktopGroups=Array.prototype.slice.call(root.querySelectorAll("[data-nexdirectory-nav-desktop-group]"));'
                . 'function closeDesktopGroups(except){desktopGroups.forEach(function(group){if(except&&group===except){return;}var trigger=group.querySelector("[data-nexdirectory-nav-desktop-trigger]");var menu=group.querySelector("[data-nexdirectory-nav-desktop-menu]");if(trigger){trigger.setAttribute("aria-expanded","false");}if(menu){menu.classList.add("hidden");}var chev=group.querySelector("[data-nexdirectory-nav-desktop-chevron]");if(chev){chev.classList.remove("rotate-180","text-brand-600");}});}'
                . 'desktopGroups.forEach(function(group){var trigger=group.querySelector("[data-nexdirectory-nav-desktop-trigger]");var menu=group.querySelector("[data-nexdirectory-nav-desktop-menu]");var chev=group.querySelector("[data-nexdirectory-nav-desktop-chevron]");if(!trigger||!menu){return;}var closeTimer=null;'
                . 'function openThis(){if(!isDesktop()){return;}closeDesktopGroups(group);menu.classList.remove("hidden");trigger.setAttribute("aria-expanded","true");if(chev){chev.classList.add("rotate-180","text-brand-600");}}'
                . 'function closeThis(){menu.classList.add("hidden");trigger.setAttribute("aria-expanded","false");if(chev){chev.classList.remove("rotate-180","text-brand-600");}}'
                . 'function cancelClose(){if(closeTimer){window.clearTimeout(closeTimer);closeTimer=null;}}'
                . 'function scheduleClose(){if(!isDesktop()){return;}cancelClose();closeTimer=window.setTimeout(function(){closeThis();},220);}'
                . 'trigger.addEventListener("click",function(e){if(!isDesktop()){return;}e.preventDefault();var isOpen=trigger.getAttribute("aria-expanded")==="true";if(isOpen){closeThis();}else{openThis();}});'
                . 'group.addEventListener("mouseenter",function(){cancelClose();openThis();});group.addEventListener("mouseleave",scheduleClose);menu.addEventListener("mouseenter",cancelClose);menu.addEventListener("mouseleave",scheduleClose);trigger.addEventListener("focus",function(){cancelClose();openThis();});trigger.addEventListener("blur",scheduleClose);});'
                . 'document.addEventListener("click",function(e){if(!root.contains(e.target)){closeDesktopGroups();setMobileOpen(false);}});'
                . 'window.addEventListener("resize",function(){if(isDesktop()){setMobileOpen(false);closeDesktopGroups();}mobileGroups.forEach(function(group){var trigger=group.querySelector("[data-nexdirectory-nav-mobile-group-toggle]");var panel=group.querySelector("[data-nexdirectory-nav-mobile-group-panel]");if(!trigger||!panel){return;}if(trigger.getAttribute("aria-expanded")==="true"){panel.style.maxHeight=panel.scrollHeight+"px";}});});'
                . 'if(behavior==="smart"){var lastScrollY=Math.max(window.scrollY||0,0);window.addEventListener("scroll",function(){if(mobileOpen){lastScrollY=Math.max(window.scrollY||0,0);return;}var nextY=Math.max(window.scrollY||0,0);if(nextY<=16||nextY<lastScrollY-6){root.classList.remove("-translate-y-full");root.classList.add("shadow-sm");}else if(nextY>lastScrollY+6&&nextY>92){root.classList.add("-translate-y-full");}if(nextY===0){root.classList.remove("shadow-sm");}lastScrollY=nextY;},{passive:true});}'
                . 'syncIcons();setMobileOpen(false);closeDesktopGroups();}'
                . 'function boot(){document.querySelectorAll("[data-nexdirectory-nav-root]").forEach(initRoot);}'
                . 'if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",boot,{once:true});}else{boot();}'
                . 'document.addEventListener("nexdirectory:spa:navigated",boot);})();'
            );
            $nav_script_printed = true;
        }
        $mobile_menu_id = 'nexdirectory-platform-mobile-menu-' . wp_rand(1000, 9999);
        $notification_panel_id = 'nexdirectory-notification-panel-' . wp_rand(1000, 9999);
        $nav_side_padding_desktop = $this->get_nav_side_padding_desktop_value('0%');
        $nav_side_padding_mobile = $this->get_page_side_padding_mobile_value('2%');

        ob_start();
        ?>
        <nav
            class="nexdirectory-platform-nav <?php echo esc_attr($position_class); ?> w-full border-b border-gray-200/60 bg-white/80 backdrop-blur-xl supports-[backdrop-filter]:bg-white/70 transition-all duration-300 shadow-[0_1px_3px_0_rgba(0,0,0,0.02)]"
            style="z-index:1100;"
            aria-label="<?php esc_attr_e('Platform navigation', 'nexdirectory'); ?>"
            data-nexdirectory-nav-root="1"
            data-nexdirectory-nav-behavior="<?php echo esc_attr($behavior); ?>"
        >
            <div class="nexdirectory-platform-nav-inner mx-auto w-full" style="--nexdirectory-nav-side-padding-desktop: <?php echo esc_attr($nav_side_padding_desktop); ?>; --nexdirectory-nav-side-padding-mobile: <?php echo esc_attr($nav_side_padding_mobile); ?>;">
                <div class="h-16 sm:h-[4.5rem] flex items-center justify-between gap-4">
                    <?php if ($show_logo) : ?>
                        <a href="<?php echo esc_url($logo_link_url); ?>" class="nexdirectory-platform-nav-brand inline-flex items-center min-w-0 shrink-0 group active:scale-95 transition-transform duration-200 origin-left">
                            <?php if ($logo_url !== '') : ?>
                                <img src="<?php echo esc_url($logo_url); ?>" alt="<?php echo esc_attr($brand_name); ?>" loading="lazy" decoding="async" class="w-auto object-contain transition-transform duration-300 group-hover:scale-105" style="height: <?php echo esc_attr($logo_size_value); ?>; max-width: <?php echo esc_attr($logo_max_width_value); ?>;">
                            <?php else : ?>
                                <span class="nexdirectory-platform-nav-brand-text text-lg font-black tracking-tight text-gray-900 group-hover:text-brand-600 transition-colors"><?php echo esc_html($brand_name); ?></span>
                            <?php endif; ?>
                        </a>
                    <?php endif; ?>

                    <div class="hidden lg:flex items-center justify-center gap-2 min-w-0 flex-1 flex-wrap <?php echo $show_logo ? 'lg:ml-4' : ''; ?>">
                        <?php $desktop_group_index = 0; ?>
                        <?php foreach ($menu_groups as $group) : ?>
                            <?php
                            $group_label = sanitize_text_field((string) ($group['label'] ?? ''));
                            $group_links = isset($group['links']) && is_array($group['links']) ? $group['links'] : [];
                            if ($group_label === '' || empty($group_links)) {
                                continue;
                            }
                            $desktop_group_key = 'nexdirectory-group-' . $desktop_group_index;
                            $desktop_dropdown_id = 'nexdirectory-platform-nav-dropdown-' . $desktop_group_index . '-' . wp_rand(1000, 9999);
                            $desktop_group_index++;
                            ?>
                            <div class="relative group/nav" data-nexdirectory-nav-desktop-group>
                                <button
                                    type="button"
                                    class="nexdirectory-platform-nav-trigger inline-flex h-10 items-center gap-1.5 rounded-full px-4 py-2 text-[14px] font-semibold text-gray-600 hover:text-gray-900 hover:bg-gray-100/80 active:scale-95 active:bg-gray-200/60 transition-all duration-200 whitespace-nowrap outline-none focus-visible:ring-2 focus-visible:ring-brand-500/50"
                                    data-nexdirectory-nav-desktop-trigger
                                    aria-expanded="false"
                                    aria-haspopup="menu"
                                    aria-controls="<?php echo esc_attr($desktop_dropdown_id); ?>"
                                >
                                    <?php echo esc_html($group_label); ?>
                                    <svg class="h-4 w-4 text-gray-400 group-hover/nav:text-gray-600 transition-transform duration-300 ease-out" data-nexdirectory-nav-desktop-chevron fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7"></path>
                                    </svg>
                                </button>
                                <div
                                    id="<?php echo esc_attr($desktop_dropdown_id); ?>"
                                    data-nexdirectory-nav-desktop-menu
                                    class="hidden absolute left-1/2 -translate-x-1/2 top-[calc(100%+0.5rem)] z-[120] min-w-[260px] rounded-2xl border border-gray-100 bg-white/95 backdrop-blur-xl p-2 shadow-[0_20px_40px_-15px_rgba(0,0,0,0.1)] nexdirectory-nav-dropdown-anim"
                                    role="menu"
                                >
                                    <div class="grid grid-cols-1 gap-1">
                                        <?php foreach ($group_links as $group_link) : ?>
                                            <?php
                                            $group_link_label = sanitize_text_field((string) ($group_link['label'] ?? ''));
                                            $group_link_url = esc_url((string) ($group_link['url'] ?? '#'));
                                            if ($group_link_label === '' || $group_link_url === '') {
                                                continue;
                                            }
                                            ?>
                                            <a
                                                href="<?php echo esc_url($group_link_url); ?>"
                                                class="nexdirectory-platform-nav-link flex items-center justify-between rounded-xl px-4 py-3 text-[14px] font-medium text-gray-700 hover:bg-brand-50/50 hover:text-brand-700 active:scale-[0.98] transition-all duration-200 group/link"
                                                role="menuitem"
                                            >
                                                <?php echo esc_html($group_link_label); ?>
                                                <svg class="w-4 h-4 opacity-0 -translate-x-2 group-hover/link:opacity-100 group-hover/link:translate-x-0 transition-all duration-300 text-brand-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
                                            </a>
                                        <?php endforeach; ?>
                                    </div>
                                </div>
                            </div>
                        <?php endforeach; ?>
                    </div>

                    <?php if ($is_member) : ?>
                        <div class="nexdirectory-notification-center nexdirectory-notification-center--dropdown" data-nexdirectory-notification-center="dropdown">
                            <button
                                type="button"
                                class="nexdirectory-notification-center__toggle"
                                data-nexdirectory-notification-toggle
                                aria-label="<?php esc_attr_e('Open notifications', 'nexdirectory'); ?>"
                                aria-expanded="false"
                                aria-haspopup="dialog"
                                aria-controls="<?php echo esc_attr($notification_panel_id); ?>"
                            >
                                <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.9" d="M15 17H9m9-6a6 6 0 10-12 0c0 3-1.5 4.5-1.5 4.5h15S18 14 18 11zm-7.75 9a2 2 0 003.5 0"></path>
                                </svg>
                                <span class="nexdirectory-notification-center__badge" data-nexdirectory-notification-count hidden>0</span>
                            </button>
                            <section
                                id="<?php echo esc_attr($notification_panel_id); ?>"
                                class="nexdirectory-notification-center__panel"
                                data-nexdirectory-notification-panel
                                role="dialog"
                                aria-modal="false"
                                aria-label="<?php esc_attr_e('Notifications', 'nexdirectory'); ?>"
                                hidden
                            >
                                <div class="nexdirectory-notification-center__header">
                                    <div>
                                        <p class="nexdirectory-notification-center__eyebrow"><?php esc_html_e('Account activity', 'nexdirectory'); ?></p>
                                        <h2><?php esc_html_e('Notifications', 'nexdirectory'); ?></h2>
                                    </div>
                                    <button type="button" class="nexdirectory-notification-center__mark-all" data-nexdirectory-notification-mark-all disabled>
                                        <?php esc_html_e('Mark all read', 'nexdirectory'); ?>
                                    </button>
                                </div>
                                <div
                                    class="nexdirectory-notification-center__list"
                                    data-nexdirectory-notification-list
                                    role="feed"
                                    aria-live="polite"
                                    aria-busy="true"
                                ></div>
                                <div class="nexdirectory-notification-center__empty" data-nexdirectory-notification-empty hidden>
                                    <span class="nexdirectory-notification-center__empty-icon" aria-hidden="true">✓</span>
                                    <strong><?php esc_html_e('You are all caught up', 'nexdirectory'); ?></strong>
                                    <span><?php esc_html_e('New activity will appear here.', 'nexdirectory'); ?></span>
                                </div>
                                <a class="nexdirectory-notification-center__footer" href="<?php echo esc_url($notifications_url); ?>">
                                    <?php esc_html_e('View notifications and inbox', 'nexdirectory'); ?>
                                    <span aria-hidden="true">→</span>
                                </a>
                            </section>
                        </div>
                    <?php endif; ?>

                    <?php if ($include_auth_cta) : ?>
                        <div class="hidden lg:flex items-center gap-3 shrink-0">
                            <?php if ($is_member) : ?>
                                <a href="<?php echo esc_url($account_url); ?>" class="nexdirectory-platform-nav-cta-link inline-flex h-10 items-center justify-center rounded-full border border-gray-200 bg-white px-4 text-sm font-bold text-gray-800 hover:border-gray-300 hover:bg-gray-50 active:scale-95 transition-all shadow-sm shadow-gray-200/20"><?php esc_html_e('My Account', 'nexdirectory'); ?></a>
                                <a href="<?php echo esc_url($logout_url); ?>" class="nexdirectory-platform-nav-cta-link nexdirectory-nav-logout-link inline-flex h-10 items-center justify-center rounded-full border border-transparent bg-[var(--nexdirectory-primary)] px-4 text-sm font-bold text-white hover:bg-[var(--nexdirectory-primary-hover)] active:scale-95 transition-all shadow-md shadow-brand-500/20"><?php esc_html_e('Logout', 'nexdirectory'); ?></a>
                            <?php else : ?>
                                <a href="<?php echo esc_url($login_url); ?>" data-nexdirectory-auth-open="1" data-nexdirectory-auth-tab="login" class="nexdirectory-platform-nav-cta-link inline-flex h-10 items-center justify-center rounded-full px-4 text-sm font-bold text-gray-600 hover:text-gray-900 hover:bg-gray-100 active:scale-95 transition-all"><?php esc_html_e('Log in', 'nexdirectory'); ?></a>
                                <a href="<?php echo esc_url($signup_url); ?>" data-nexdirectory-auth-open="1" data-nexdirectory-auth-tab="signup" class="nexdirectory-platform-nav-cta-link nexdirectory-nav-signup-link inline-flex h-10 items-center justify-center rounded-full border border-transparent bg-[var(--nexdirectory-primary)] px-5 text-sm font-bold text-white hover:bg-[var(--nexdirectory-primary-hover)] active:scale-95 transition-all shadow-md shadow-brand-500/20"><?php esc_html_e('Sign Up', 'nexdirectory'); ?></a>
                            <?php endif; ?>
                        </div>
                    <?php endif; ?>

                    <button
                        type="button"
                        class="lg:hidden inline-flex h-11 w-11 items-center justify-center rounded-full bg-gray-100/80 text-gray-700 hover:bg-gray-200 hover:text-gray-900 active:scale-90 transition-all duration-300 ml-auto"
                        data-nexdirectory-nav-toggle
                        aria-expanded="false"
                        aria-controls="<?php echo esc_attr($mobile_menu_id); ?>"
                    >
                        <span class="screen-reader-text"><?php esc_html_e('Toggle menu', 'nexdirectory'); ?></span>
                        <svg class="h-5 w-5 transition-transform duration-300" data-nexdirectory-nav-icon-open fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
                        <svg class="h-5 w-5 hidden transition-transform duration-300" data-nexdirectory-nav-icon-close fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
                    </button>
                </div>
            </div>

            <div class="nexdirectory-nav-mobile-shell lg:hidden hidden opacity-0 pointer-events-none transition-opacity duration-300" data-nexdirectory-nav-mobile-shell style="position:fixed;left:0;right:0;top:0;height:100dvh;z-index:1200;">
                <div class="absolute inset-0 bg-gray-900/60 backdrop-blur-sm transition-opacity duration-300" data-nexdirectory-nav-close></div>
                <aside id="<?php echo esc_attr($mobile_menu_id); ?>" data-nexdirectory-nav-mobile-panel class="absolute inset-y-0 right-0 w-[90%] max-w-[380px] bg-[#f8fafc] shadow-[0_24px_80px_rgba(0,0,0,0.3)] flex flex-col translate-x-full opacity-0 transition-all duration-[350ms] ease-[cubic-bezier(0.2,0.8,0.2,1)] rounded-l-[2rem] overflow-hidden" style="z-index:1201;">
                    
                    <div class="px-6 pt-safe pb-4 bg-white/80 backdrop-blur-md border-b border-gray-200/50 sticky top-0 z-10 flex items-center justify-between" style="padding-top: max(1.5rem, env(safe-area-inset-top));">
                        <div>
                            <strong class="text-xl font-black tracking-tight text-gray-900"><?php echo esc_html($brand_name); ?></strong>
                        </div>
                        <button type="button" class="inline-flex h-10 w-10 items-center justify-center rounded-full bg-gray-100 text-gray-600 hover:bg-gray-200 hover:text-gray-900 active:scale-90 transition-all duration-200" data-nexdirectory-nav-close>
                            <span class="screen-reader-text"><?php esc_html_e('Close menu', 'nexdirectory'); ?></span>
                            <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12"></path></svg>
                        </button>
                    </div>
                    
                    <div class="flex-1 overflow-y-auto px-4 py-6 space-y-4">
                        <?php $mobile_group_index = 0; ?>
                        <?php foreach ($menu_groups as $group) : ?>
                            <?php
                            $group_label = sanitize_text_field((string) ($group['label'] ?? ''));
                            $group_links = isset($group['links']) && is_array($group['links']) ? $group['links'] : [];
                            if ($group_label === '' || empty($group_links)) {
                                continue;
                            }
                            $mobile_group_open = $mobile_group_index === 0;
                            // Stagger animation timing
                            $stagger_delay = ($mobile_group_index * 60) . 'ms';
                            ?>
                            <section class="rounded-2xl bg-gray-100/50 border border-gray-200/60 overflow-hidden nexdirectory-mobile-stagger" style="animation-delay: <?php echo esc_attr($stagger_delay); ?>;" data-nexdirectory-nav-mobile-group>
                                <button type="button" class="nexdirectory-platform-nav-mobile-trigger w-full px-5 py-4 text-[13px] font-bold uppercase tracking-wider text-gray-500 flex items-center justify-between text-left active:bg-gray-100/50 transition-colors focus:outline-none" data-nexdirectory-nav-mobile-group-toggle aria-expanded="<?php echo $mobile_group_open ? 'true' : 'false'; ?>">
                                    <span><?php echo esc_html($group_label); ?></span>
                                    <div class="w-8 h-8 rounded-full bg-white shadow-sm flex items-center justify-center shrink-0">
                                        <svg class="h-4 w-4 text-gray-500 transition-transform duration-[350ms] ease-out <?php echo $mobile_group_open ? 'rotate-180 text-brand-600' : ''; ?>" data-nexdirectory-nav-mobile-group-chevron fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
                                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7"></path>
                                        </svg>
                                    </div>
                                </button>
                                <div class="px-2 pb-2 space-y-1 <?php echo $mobile_group_open ? '' : 'hidden'; ?>" data-nexdirectory-nav-mobile-group-panel>
                                    <?php foreach ($group_links as $group_link) : ?>
                                        <a href="<?php echo esc_url((string) ($group_link['url'] ?? '#')); ?>" data-nexdirectory-nav-close-link class="nexdirectory-platform-nav-mobile-link flex items-center justify-between rounded-xl px-4 py-3.5 text-[15px] font-semibold text-gray-700 bg-transparent hover:bg-white active:bg-white active:scale-[0.98] transition-all duration-200 group/moblink">
                                            <?php echo esc_html((string) ($group_link['label'] ?? '')); ?>
                                            <svg class="w-4 h-4 text-gray-300 group-hover/moblink:text-brand-500 group-hover/moblink:translate-x-1 transition-all duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
                                        </a>
                                    <?php endforeach; ?>
                                </div>
                            </section>
                            <?php $mobile_group_index++; ?>
                        <?php endforeach; ?>
                    </div>

                    <?php if ($include_auth_cta) : ?>
                        <div class="p-5 pb-safe bg-white border-t border-gray-200/60 shadow-[0_-4px_16px_rgba(0,0,0,0.02)] nexdirectory-mobile-stagger" style="animation-delay: <?php echo esc_attr(($mobile_group_index * 60) + 100); ?>ms;">
                            <div class="grid grid-cols-1 gap-3">
                                <?php if ($is_member) : ?>
                                    <a href="<?php echo esc_url($account_url); ?>" data-nexdirectory-nav-close-link class="nexdirectory-platform-nav-mobile-cta-link inline-flex h-12 w-full items-center justify-center rounded-xl border-2 border-gray-100 bg-gray-50 text-[15px] font-bold text-gray-800 active:scale-95 transition-all"><?php esc_html_e('My Account', 'nexdirectory'); ?></a>
                                    <a href="<?php echo esc_url($logout_url); ?>" data-nexdirectory-nav-close-link class="nexdirectory-platform-nav-mobile-cta-link nexdirectory-nav-logout-link inline-flex h-12 w-full items-center justify-center rounded-xl bg-[var(--nexdirectory-primary)] text-[15px] font-bold text-white shadow-lg shadow-brand-500/25 active:scale-95 transition-all"><?php esc_html_e('Logout', 'nexdirectory'); ?></a>
                                <?php else : ?>
                                    <a href="<?php echo esc_url($login_url); ?>" data-nexdirectory-nav-close-link data-nexdirectory-auth-open="1" data-nexdirectory-auth-tab="login" class="nexdirectory-platform-nav-mobile-cta-link inline-flex h-12 w-full items-center justify-center rounded-xl border-2 border-gray-100 bg-gray-50 text-[15px] font-bold text-gray-800 hover:border-gray-200 active:scale-95 transition-all"><?php esc_html_e('Log In', 'nexdirectory'); ?></a>
                                    <a href="<?php echo esc_url($signup_url); ?>" data-nexdirectory-nav-close-link data-nexdirectory-auth-open="1" data-nexdirectory-auth-tab="signup" class="nexdirectory-platform-nav-mobile-cta-link nexdirectory-nav-signup-link inline-flex h-12 w-full items-center justify-center rounded-xl bg-[var(--nexdirectory-primary)] text-[15px] font-bold text-white shadow-lg shadow-brand-500/25 active:scale-95 transition-all"><?php esc_html_e('Sign Up', 'nexdirectory'); ?></a>
                                <?php endif; ?>
                            </div>
                        </div>
                    <?php endif; ?>
                </aside>
            </div>
        </nav>
        <?php
        return (string) ob_get_clean();
    }

    /**
     * Filters body classes to hide native theme page titles on platform pages if configured.
     */
    public function filter_body_class_for_plugin_pages($classes) {
        if (!is_array($classes)) $classes = [];

        $is_app_page = $this->is_app_page();
        if (!$this->is_platform_frontend_navigation_context()) {
            return array_values(array_unique($classes));
        }

        $classes[] = 'nexdirectory-plugin-page';
        if ($is_app_page) {
            $classes[] = 'nexdirectory-app-page';
        }
        $has_platform_sticky_nav = $this->is_platform_frontend_navigation_context()
            && $this->should_show_frontend_sticky_nav_for_current_user();
        if ($has_platform_sticky_nav) {
            $classes[] = 'nexdirectory-has-platform-sticky-nav';
            $classes[] = 'nexdirectory-theme-override-hide-header';
        }
        if ($is_app_page) {
            $app_view = $this->get_app_view();
            if ($app_view !== '' && $app_view !== 'app') {
                $classes[] = 'nexdirectory-view-' . sanitize_html_class($app_view);
            }
        }
        if ($this->should_hide_theme_page_titles_now()) {
            $classes[] = 'nexdirectory-hide-theme-page-title';
        }
        if ($this->is_theme_override_enabled('full_width')) {
            $classes[] = 'nexdirectory-theme-override-full-width';
        }
        if ($this->is_theme_override_enabled('remove_padding')) {
            $classes[] = 'nexdirectory-theme-override-no-padding';
        }
        if ($this->is_theme_override_enabled('hide_comments')) {
            $classes[] = 'nexdirectory-theme-override-hide-comments';
        }
        if ($this->is_theme_override_enabled('hide_header')) {
            $classes[] = 'nexdirectory-theme-override-hide-header';
        }
        if ($this->is_theme_override_enabled('reset_theme_styles')) {
            $classes[] = 'nexdirectory-theme-override-reset';
        }

        return array_values(array_unique($classes));
    }

    /**
     * Enqueues scoped app-page layout isolation styles only on plugin app pages.
     *
     * @return void
     */
    public function enqueue_app_page_assets() {
        if (is_admin() || (function_exists('wp_doing_ajax') && wp_doing_ajax())) {
            return;
        }
        if (!$this->is_app_page()) {
            return;
        }

        $css_relative = 'assets/css/nexdirectory-app-page.css';
        $css_path = plugin_dir_path(NEXDIRECTORY_PLUGIN_FILE) . $css_relative;
        if (!is_readable($css_path)) {
            return;
        }

        wp_enqueue_style(
            'nexdirectory-app-page',
            plugins_url($css_relative, NEXDIRECTORY_PLUGIN_FILE),
            [],
            (string) filemtime($css_path)
        );

        if ($this->is_option_enabled('nexdirectory_enable_smooth_scrolling', '0')) {
            // Keep CSS scroll behavior on auto; wheel/touch smoothing is handled by JS runtime.
            wp_add_inline_style('nexdirectory-app-page', 'html,body{scroll-behavior:auto;}');
        }
    }

    /**
     * Forces plugin app pages through a lightweight plugin template shell.
     *
     * @param string $template
     * @return string
     */
    public function filter_template_include_for_app_pages($template) {
        if (!$this->is_app_page()) {
            return (string) $template;
        }
        if (!$this->is_theme_override_master_enabled()) {
            return (string) $template;
        }

        $override_template = plugin_dir_path(NEXDIRECTORY_PLUGIN_FILE) . 'templates/nexdirectory-app-page.php';
        if (is_readable($override_template)) {
            return $override_template;
        }

        return (string) $template;
    }

    /**
     * Removes theme-level featured image output on platform frontend pages.
     *
     * Platform templates already handle hero imagery, so this prevents duplicate
     * oversized thumbnails from rendering above the sticky nav.
     *
     * @param string $html
     * @param int $post_id
     * @param int $post_thumbnail_id
     * @param string|array<int, int>|array<string, string> $size
     * @param string|array<string, string> $attr
     * @return string
     */
    public function filter_platform_featured_image_html($html, $post_id = 0, $post_thumbnail_id = 0, $size = 'post-thumbnail', $attr = '') {
        if (is_admin() || (function_exists('wp_doing_ajax') && wp_doing_ajax())) {
            return (string) $html;
        }
        if (defined('REST_REQUEST') && REST_REQUEST) {
            return (string) $html;
        }
        if (!$this->is_platform_frontend_navigation_context()) {
            return (string) $html;
        }
        if (!$this->is_theme_override_enabled('hide_featured_image')) {
            return (string) $html;
        }

        return '';
    }

    /**
     * Disables comments on plugin pages when theme override asks for it.
     *
     * @param bool $open
     * @param int $post_id
     * @return bool
     */
    public function filter_comments_open_for_plugin_pages($open, $post_id = 0) {
        if (is_admin() || (function_exists('wp_doing_ajax') && wp_doing_ajax())) {
            return (bool) $open;
        }
        if (!$this->is_platform_frontend_navigation_context()) {
            return (bool) $open;
        }
        if ($this->is_theme_override_enabled('hide_comments')) {
            return false;
        }
        return (bool) $open;
    }

    /**
     * Injects a compatibility CSS reset layer for plugin pages.
     *
     * @return void
     */
    public function output_plugin_page_theme_override_css() {
        if (is_admin() || (function_exists('wp_doing_ajax') && wp_doing_ajax())) {
            return;
        }
        if (!$this->is_platform_frontend_navigation_context()) {
            return;
        }

        $full_width = $this->is_theme_override_enabled('full_width');
        $remove_padding = $this->is_theme_override_enabled('remove_padding');
        $hide_title = $this->should_hide_theme_page_titles_now();
        $hide_comments = $this->is_theme_override_enabled('hide_comments');
        $hide_header = $this->is_theme_override_enabled('hide_header');
        $reset_theme_styles = $this->is_theme_override_enabled('reset_theme_styles');
        if (!$full_width && !$remove_padding && !$hide_title && !$hide_comments && !$hide_header && !$reset_theme_styles) {
            return;
        }

        $css = '';
        if ($full_width) {
            $css .= '
body.nexdirectory-plugin-page .site,
body.nexdirectory-plugin-page #page,
body.nexdirectory-plugin-page #content,
body.nexdirectory-plugin-page #main,
body.nexdirectory-plugin-page main,
body.nexdirectory-plugin-page .site-content,
body.nexdirectory-plugin-page .content-area,
body.nexdirectory-plugin-page .site-main,
body.nexdirectory-plugin-page .entry-content,
body.nexdirectory-plugin-page .entry,
body.nexdirectory-plugin-page .post,
body.nexdirectory-plugin-page article.page,
body.nexdirectory-plugin-page article.post,
body.nexdirectory-plugin-page .container,
body.nexdirectory-plugin-page .container-fluid,
body.nexdirectory-plugin-page .wrap,
body.nexdirectory-plugin-page .site-inner,
body.nexdirectory-plugin-page .ast-container,
body.nexdirectory-plugin-page .ast-article-single,
body.nexdirectory-plugin-page .inside-article,
body.nexdirectory-plugin-page #primary,
body.nexdirectory-plugin-page .primary,
body.nexdirectory-plugin-page .main-content {
    width: 100% !important;
    max-width: none !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
}';
        }
        if ($remove_padding) {
            $css .= '
body.nexdirectory-plugin-page .site-content,
body.nexdirectory-plugin-page .content-area,
body.nexdirectory-plugin-page .site-main,
body.nexdirectory-plugin-page #content,
body.nexdirectory-plugin-page #main,
body.nexdirectory-plugin-page main,
body.nexdirectory-plugin-page article.page,
body.nexdirectory-plugin-page article.post,
body.nexdirectory-plugin-page .entry-content,
body.nexdirectory-plugin-page .container,
body.nexdirectory-plugin-page .container-fluid,
body.nexdirectory-plugin-page .wrap,
body.nexdirectory-plugin-page .site-inner,
body.nexdirectory-plugin-page .ast-container,
body.nexdirectory-plugin-page .inside-article {
    padding-top: 0 !important;
    padding-bottom: 0 !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
}';
        }
        if ($hide_title) {
            $css .= '
body.nexdirectory-plugin-page .entry-title,
body.nexdirectory-plugin-page .page-title,
body.nexdirectory-plugin-page .wp-block-post-title,
body.nexdirectory-plugin-page .entry-meta,
body.nexdirectory-plugin-page .post-meta,
body.nexdirectory-plugin-page .breadcrumbs,
body.nexdirectory-plugin-page .breadcrumb,
body.nexdirectory-plugin-page .yoast-breadcrumb,
body.nexdirectory-plugin-page .entry-header,
body.nexdirectory-plugin-page .page-header,
body.nexdirectory-plugin-page .ast-single-post-header,
body.nexdirectory-plugin-page .post-thumbnail,
body.nexdirectory-plugin-page .featured-image,
body.nexdirectory-plugin-page .entry-media,
body.nexdirectory-hide-theme-page-title .entry-header,
body.nexdirectory-hide-theme-page-title .page-header {
    display: none !important;
}';
        }
        if ($hide_comments) {
            $css .= '
body.nexdirectory-plugin-page #comments,
body.nexdirectory-plugin-page .comments-area,
body.nexdirectory-plugin-page .comment-respond,
body.nexdirectory-plugin-page .post-comments {
    display: none !important;
}';
        }
        if ($hide_header) {
            $css .= '
body.nexdirectory-plugin-page #header,
body.nexdirectory-plugin-page .site-header,
body.nexdirectory-plugin-page #masthead,
body.nexdirectory-plugin-page #site-header,
body.nexdirectory-plugin-page .ast-header-wrap,
body.nexdirectory-plugin-page .main-header-bar,
body.nexdirectory-plugin-page .ast-mobile-header-wrap,
body.nexdirectory-plugin-page .ast-desktop-header-content,
body.nexdirectory-plugin-page .header-main-layout-1,
body.nexdirectory-plugin-page .elementor-location-header,
body.nexdirectory-plugin-page .site-branding,
body.nexdirectory-plugin-page .site-title,
body.nexdirectory-plugin-page .wp-block-site-title {
    display: none !important;
}';
        }
        if ($reset_theme_styles) {
            $css .= '
body.nexdirectory-plugin-page .hentry,
body.nexdirectory-plugin-page article.page,
body.nexdirectory-plugin-page article.post,
body.nexdirectory-plugin-page .entry-content,
body.nexdirectory-plugin-page .page-content,
body.nexdirectory-plugin-page .widget-area,
body.nexdirectory-plugin-page #secondary,
body.nexdirectory-plugin-page .sidebar {
    background: transparent !important;
    border: 0 !important;
    box-shadow: none !important;
}';
            $css .= '
body.nexdirectory-plugin-page .widget-area,
body.nexdirectory-plugin-page #secondary,
body.nexdirectory-plugin-page .sidebar {
    display: none !important;
}';
            $css .= '
body.nexdirectory-plugin-page .elementor-section.elementor-section-boxed > .elementor-container,
body.nexdirectory-plugin-page .elementor-container {
    max-width: 100% !important;
}';
            $css .= '
body.nexdirectory-plugin-page header.site-header .site-branding,
body.nexdirectory-plugin-page .site-header .custom-logo-link + .site-title,
body.nexdirectory-plugin-page .site-header .site-title,
body.nexdirectory-plugin-page .site-header .site-description {
    display: none !important;
}';
        }

        if ($css === '') {
            return;
        }

        $style_markup = $this->render_inline_style_asset('theme-override-style', $css);
        if ($style_markup !== '') {
            $style_markup = NEXDIRECTORY_Escaping::prepare_markup_for_wp_kses($style_markup);
            echo wp_kses($style_markup, NEXDIRECTORY_Escaping::allowed_html($style_markup));
        }
    }

    private function is_theme_override_master_enabled() {
        return $this->is_option_enabled('nexdirectory_theme_override_all_plugin_pages', '1');
    }

    private function is_theme_override_enabled($feature_key) {
        if ($this->is_theme_override_master_enabled() && $feature_key !== 'hide_header') {
            return true;
        }

        $map = [
            'full_width' => 'nexdirectory_theme_override_full_width',
            'remove_padding' => 'nexdirectory_theme_override_remove_padding',
            'hide_title' => 'nexdirectory_theme_override_hide_title',
            'hide_comments' => 'nexdirectory_theme_override_hide_comments',
            'hide_featured_image' => 'nexdirectory_theme_override_hide_featured_image',
            'hide_header' => 'nexdirectory_theme_override_hide_header',
            'reset_theme_styles' => 'nexdirectory_theme_override_reset_theme_styles',
        ];
        $option_key = isset($map[$feature_key]) ? (string) $map[$feature_key] : '';
        if ($option_key === '') {
            return false;
        }

        return $this->is_option_enabled($option_key, '1');
    }

    private function should_hide_theme_page_titles_now() {
        if (!$this->is_theme_override_enabled('hide_title') && !$this->is_option_enabled('nexdirectory_hide_theme_titles_on_plugin_pages', '0')) return false;
        if (is_admin() || wp_doing_ajax() || (defined('REST_REQUEST') && REST_REQUEST)) return false;
        if (!is_singular()) return false;

        $queried_id = absint((int) get_queried_object_id());
        if ($queried_id > 0) {
            $platform_page_ids = $this->get_platform_page_ids_for_title_hiding();
            if (in_array($queried_id, $platform_page_ids, true)) {
                return true;
            }
        }

        return $this->current_page_uses_platform_shortcode();
    }

    private function get_platform_page_ids_for_title_hiding() {
        // Omitting full map array here for brevity, assumes lookup of core page configs (account, submit, dashboard, etc.)
        // Returns an array of mapped Page IDs configured in options.
        static $cached_ids = null;
        if (is_array($cached_ids)) return $cached_ids;
        
        $ids = [];
        $options = [
            'nexdirectory_page_events_url', 'nexdirectory_page_events_map_url', 'nexdirectory_page_events_calendar_url', 
            'nexdirectory_page_submit_event_url', 'nexdirectory_page_my_events_url',
            'nexdirectory_page_my_tickets_url', 'nexdirectory_page_whos_attending_url',
            'nexdirectory_page_event_webapp_url',
            'nexdirectory_page_whats_new_url', 'nexdirectory_page_notifications_url', 'nexdirectory_page_account_url', 'nexdirectory_page_login_signup_url'
        ];
        $options = array_values(array_filter(array_map(
            'sanitize_key',
            (array) apply_filters('nexdirectory_extension_platform_page_options', $options)
        )));
        
        foreach ($options as $opt) {
            $url = esc_url_raw((string) get_option($opt, ''));
            if ($url !== '') {
                $page_id = absint((int) url_to_postid($url));
                if ($page_id > 0) $ids[$page_id] = $page_id;
            }
        }
        
        $cached_ids = array_values($ids);
        return $cached_ids;
    }

    private function current_page_uses_platform_shortcode() {
        $post = $this->resolve_app_page_post();
        if (!$post instanceof WP_Post) {
            return false;
        }

        if ((string) get_post_meta((int) $post->ID, '_nexdirectory_is_app_page', true) === '1') {
            return true;
        }

        return $this->extract_app_view_from_content((string) $post->post_content) !== '';
    }

    /**
     * Renders top-right auth/account links when sticky nav is not handling them.
     *
     * @return string
     */
    public function render_frontend_auth_links() {
        if (!$this->is_option_enabled('nexdirectory_show_frontend_auth_links', '1')) {
            return '';
        }
        if ($this->sticky_nav_handles_auth_cta_for_current_user()) {
            return '';
        }

        $wrapper_class = 'nexdirectory-page-auth-links';
        $redirect_to = $this->current_url();
        $login_base = $this->get_custom_login_page_url();
        if ($login_base === '') {
            $login_url = wp_login_url($redirect_to);
        } else {
            $login_url = nexdirectory_add_query_arg('redirect_to', $redirect_to, $login_base);
        }

        if (is_user_logged_in()) {
            $account_url = $this->get_platform_page_url('nexdirectory_page_account_url', ['account', 'my-account'], home_url('/'));
            $logout_url = wp_logout_url($login_url);
            return '<div class="' . esc_attr($wrapper_class) . '" role="navigation" aria-label="' . esc_attr__('Account actions', 'nexdirectory') . '">'
                . '<a class="nexdirectory-page-auth-link" href="' . esc_url($account_url) . '">' . esc_html__('My Account', 'nexdirectory') . '</a>'
                . '<a class="nexdirectory-page-auth-link nexdirectory-page-auth-link-primary" href="' . esc_url($logout_url) . '">' . esc_html__('Logout', 'nexdirectory') . '</a>'
                . '</div>';
        }

        $signup_url = $login_base === ''
            ? nexdirectory_add_query_arg('redirect_to', $redirect_to, wp_registration_url())
            : nexdirectory_add_query_arg('nexdirectory_auth', 'signup', $login_url);
        return '<div class="' . esc_attr($wrapper_class) . '" role="navigation" aria-label="' . esc_attr__('Authentication', 'nexdirectory') . '">'
            . '<a class="nexdirectory-page-auth-link" data-nexdirectory-auth-open="1" data-nexdirectory-auth-tab="login" href="' . esc_url($login_url) . '">' . esc_html__('Login', 'nexdirectory') . '</a>'
            . '<a class="nexdirectory-page-auth-link nexdirectory-page-auth-link-primary" data-nexdirectory-auth-open="1" data-nexdirectory-auth-tab="signup" href="' . esc_url($signup_url) . '">' . esc_html__('Sign Up', 'nexdirectory') . '</a>'
            . '</div>';
    }

    /**
     * Renders global auth modal so login/signup can happen without leaving the page.
     *
     * @return string
     */
    private function render_frontend_auth_modal() {
        if (is_user_logged_in()) {
            return '';
        }

        static $printed = false;
        if ($printed) {
            return '';
        }
        $printed = true;

        $redirect_to = $this->current_url();
        $core_login_action = network_site_url('wp-login.php', 'login_post');
        $core_register_url = nexdirectory_add_query_arg('redirect_to', $redirect_to, wp_registration_url());
        $core_lostpassword_url = wp_lostpassword_url($redirect_to);
        $login_base = $this->get_custom_login_page_url();
        if (!is_string($login_base) || trim($login_base) === '') {
            $login_base = home_url('/login-signup/');
        }
        $login_path = (string) wp_parse_url($login_base, PHP_URL_PATH);
        $login_path = $login_path !== '' ? untrailingslashit($login_path) : '';
        if ($login_path === '') {
            $login_path = '/login-signup';
        }

        $claim_page_url = $this->get_platform_page_url(
            'nexdirectory_claim_listing_page_url',
            ['claim-listing', 'claim'],
            home_url('/claim-listing/')
        );
        $claim_path = (string) wp_parse_url((string) $claim_page_url, PHP_URL_PATH);
        $claim_path = $claim_path !== '' ? untrailingslashit($claim_path) : '';

        ob_start();
        ?>
        <?php
        $global_auth_style_markup = NEXDIRECTORY_Escaping::prepare_markup_for_wp_kses((string) $this->render_local_style_asset('global-auth-modal-style', 'assets/generated/global-auth-modal-style.css'));
        echo wp_kses($global_auth_style_markup, NEXDIRECTORY_Escaping::allowed_html($global_auth_style_markup)); ?>
        <div
            id="nexdirectory-global-auth-modal"
            class="nexdirectory-global-auth-modal"
            hidden
            data-login-path="<?php echo esc_attr($login_path); ?>"
            data-claim-path="<?php echo esc_attr($claim_path); ?>"
            data-default-redirect="<?php echo esc_attr($redirect_to); ?>"
        >
            <div class="nexdirectory-global-auth-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="nexdirectory-global-auth-title">
                <div class="nexdirectory-global-auth-modal__head">
                    <h2 class="nexdirectory-global-auth-modal__title" id="nexdirectory-global-auth-title"><?php esc_html_e('Sign In Or Join', 'nexdirectory'); ?></h2>
                    <button type="button" class="nexdirectory-global-auth-modal__close" data-nexdirectory-auth-close aria-label="<?php esc_attr_e('Close', 'nexdirectory'); ?>">&times;</button>
                </div>
                <div class="nexdirectory-global-auth-modal__body">
                    <div class="nexdirectory-global-auth-tabs" role="tablist" aria-label="<?php esc_attr_e('Authentication tabs', 'nexdirectory'); ?>">
                        <button type="button" id="nexdirectory-global-auth-login-tab" class="nexdirectory-global-auth-tab is-active" data-nexdirectory-auth-tab-trigger="login" role="tab" aria-controls="nexdirectory-global-auth-login-panel" aria-selected="true" tabindex="0"><?php esc_html_e('Log In', 'nexdirectory'); ?></button>
                        <button type="button" id="nexdirectory-global-auth-signup-tab" class="nexdirectory-global-auth-tab" data-nexdirectory-auth-tab-trigger="signup" role="tab" aria-controls="nexdirectory-global-auth-signup-panel" aria-selected="false" tabindex="-1"><?php esc_html_e('Sign Up', 'nexdirectory'); ?></button>
                    </div>

                    <section id="nexdirectory-global-auth-login-panel" class="nexdirectory-global-auth-panel" data-nexdirectory-auth-panel="login" role="tabpanel" aria-labelledby="nexdirectory-global-auth-login-tab">
                        <form class="nexdirectory-global-auth-form" method="post" action="<?php echo esc_url($core_login_action); ?>">
                            <input type="hidden" name="redirect_to" class="nexdirectory-global-auth-redirect" value="<?php echo esc_attr($redirect_to); ?>">
                            <input type="hidden" name="testcookie" value="1">
                            <p>
                                <label for="nexdirectory-global-auth-login-identifier"><?php esc_html_e('Username or Email', 'nexdirectory'); ?></label>
                                <input id="nexdirectory-global-auth-login-identifier" type="text" name="log" required autocomplete="username">
                            </p>
                            <p>
                                <label for="nexdirectory-global-auth-login-password"><?php esc_html_e('Password', 'nexdirectory'); ?></label>
                                <input id="nexdirectory-global-auth-login-password" type="password" name="pwd" required autocomplete="current-password">
                            </p>
                            <label class="nexdirectory-global-auth-check"><input type="checkbox" name="rememberme" value="forever"><span><?php esc_html_e('Remember me', 'nexdirectory'); ?></span></label>
                            <button type="submit" class="nexdirectory-global-auth-submit"><?php esc_html_e('Log In', 'nexdirectory'); ?></button>
                            <button type="button" class="nexdirectory-global-auth-minor" data-nexdirectory-auth-switch="forgot"><?php esc_html_e('Forgot your password?', 'nexdirectory'); ?></button>
                        </form>
                    </section>

                    <section id="nexdirectory-global-auth-signup-panel" class="nexdirectory-global-auth-panel" data-nexdirectory-auth-panel="signup" role="tabpanel" aria-labelledby="nexdirectory-global-auth-signup-tab" hidden>
                        <div class="nexdirectory-global-auth-form">
                            <p><?php esc_html_e('Create your account through the standard WordPress registration screen.', 'nexdirectory'); ?></p>
                            <a class="nexdirectory-global-auth-submit" href="<?php echo esc_url($core_register_url); ?>" data-nexdirectory-auth-core="1"><?php esc_html_e('Open Registration', 'nexdirectory'); ?></a>
                        </div>
                    </section>

                    <section id="nexdirectory-global-auth-forgot-panel" class="nexdirectory-global-auth-panel" data-nexdirectory-auth-panel="forgot" role="region" aria-label="<?php esc_attr_e('Password recovery', 'nexdirectory'); ?>" hidden>
                        <form class="nexdirectory-global-auth-form" method="post" action="<?php echo esc_url($core_lostpassword_url); ?>">
                            <input type="hidden" name="redirect_to" class="nexdirectory-global-auth-redirect" value="<?php echo esc_attr($redirect_to); ?>">
                            <p>
                                <label for="nexdirectory-global-auth-forgot-identifier"><?php esc_html_e('Username or Email', 'nexdirectory'); ?></label>
                                <input id="nexdirectory-global-auth-forgot-identifier" type="text" name="user_login" required autocomplete="username email">
                            </p>
                            <button type="submit" class="nexdirectory-global-auth-submit"><?php esc_html_e('Send Reset Link', 'nexdirectory'); ?></button>
                            <button type="button" class="nexdirectory-global-auth-minor" data-nexdirectory-auth-switch="login"><?php esc_html_e('Back to log in', 'nexdirectory'); ?></button>
                        </form>
                    </section>
                </div>
            </div>
        </div>
        <?php
        $this->render_local_script_asset('global-auth-modal-script', 'assets/generated/global-auth-modal-script.js');
        ?>
        <?php
        return (string) ob_get_clean();
    }

    /**
     * Outputs global auth links in footer if not already rendered.
     *
     * @return void
     */
    public function output_frontend_auth_links_global() {
        if (is_admin()) {
            return;
        }
        if ($this->is_platform_frontend_navigation_context() && did_action('nexdirectory_frontend_sticky_nav_rendered') === 0) {
            $sticky_markup = $this->render_frontend_sticky_nav();
            if ($sticky_markup !== '') {
                $sticky_markup = NEXDIRECTORY_Escaping::prepare_markup_for_wp_kses('<div class="nxd-ui" data-nexdirectory-tailwind-scope="1">' . (string) $sticky_markup . '</div>');
                echo wp_kses($sticky_markup, NEXDIRECTORY_Escaping::allowed_html($sticky_markup));
                do_action('nexdirectory_frontend_sticky_nav_rendered');
            }
        }
        if (did_action('nexdirectory_frontend_auth_links_rendered') === 0) {
            $auth_markup = $this->render_frontend_auth_links();
            if ($auth_markup !== '') {
                $auth_markup = NEXDIRECTORY_Escaping::prepare_markup_for_wp_kses('<div class="nxd-ui" data-nexdirectory-tailwind-scope="1">' . (string) $auth_markup . '</div>');
                echo wp_kses($auth_markup, NEXDIRECTORY_Escaping::allowed_html($auth_markup));
                do_action('nexdirectory_frontend_auth_links_rendered');
            }
        }
        $auth_page_path = (string) wp_parse_url($this->get_custom_login_page_url(), PHP_URL_PATH);
        $current_path = (string) wp_parse_url($this->current_url(), PHP_URL_PATH);
        $is_dedicated_auth_page = $auth_page_path !== ''
            && untrailingslashit($auth_page_path) === untrailingslashit($current_path);
        if (!$is_dedicated_auth_page) {
            $auth_modal_markup = $this->render_frontend_auth_modal();
            if ($auth_modal_markup !== '') {
                $auth_modal_markup = NEXDIRECTORY_Escaping::prepare_markup_for_wp_kses('<div class="nxd-ui" data-nexdirectory-tailwind-scope="1">' . (string) $auth_modal_markup . '</div>');
                echo wp_kses($auth_modal_markup, NEXDIRECTORY_Escaping::allowed_html($auth_modal_markup));
            }
        }
        if ($this->is_platform_frontend_navigation_context() && did_action('nexdirectory_frontend_footer_rendered') === 0) {
            $footer_markup = $this->render_frontend_platform_footer();
            if ($footer_markup !== '') {
                $footer_markup = NEXDIRECTORY_Escaping::prepare_markup_for_wp_kses('<div class="nxd-ui" data-nexdirectory-tailwind-scope="1">' . (string) $footer_markup . '</div>');
                echo wp_kses($footer_markup, NEXDIRECTORY_Escaping::allowed_html($footer_markup));
                do_action('nexdirectory_frontend_footer_rendered');
            }
        }
    }
}
