<?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;
}

trait NEXDIRECTORY_Admin_Trait {
    public function maybe_redirect_after_activation() {
        if (!is_admin()) {
            return;
        }
        if (!current_user_can('manage_options')) {
            return;
        }
        $activation_flag = (string) get_option('nexdirectory_activation_redirect_import', '0');
        $onboarding_done = (string) get_option('nexdirectory_onboarding_complete', '0') === '1';
        if ($onboarding_done) {
            if ($activation_flag === '1') {
                delete_option('nexdirectory_activation_redirect_import');
            }
            return;
        }
        if ($activation_flag !== '1') {
            return;
        }
        if (function_exists('wp_doing_ajax') && wp_doing_ajax()) {
            return;
        }
        if (isset($_GET['activate-multi'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            if ($activation_flag === '1') {
                delete_option('nexdirectory_activation_redirect_import');
            }
            return;
        }
        $page = isset($_GET['page']) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            ? sanitize_key((string) wp_unslash($_GET['page'])) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            : '';
        if ($page === 'nexdirectory-onboarding') {
            if ($activation_flag === '1') {
                delete_option('nexdirectory_activation_redirect_import');
            }
            return;
        }
        if ($activation_flag === '1') {
            delete_option('nexdirectory_activation_redirect_import');
            $last_step = absint((int) get_option('nexdirectory_onboarding_last_step', 1));
            if ($last_step < 1 || $last_step > 6) {
                $last_step = 1;
            }
            wp_safe_redirect(
                nexdirectory_add_query_arg(
                    [
                        'page' => 'nexdirectory-onboarding',
                        'step' => $last_step,
                    ],
                    admin_url('admin.php')
                )
            );
            exit;
        }
    }


    public function maybe_enable_theme_menu_support_for_wp_menu_source() {
        $wp_menu_source_enabled = (string) get_option('nexdirectory_frontend_sticky_nav_use_wp_menus', '0') === '1'
            || (string) get_option('nexdirectory_frontend_footer_use_wp_menus', '0') === '1';
        if (!$wp_menu_source_enabled) {
            return;
        }
        if (!current_theme_supports('menus')) {
            add_theme_support('menus');
        }
    }


    public function register_wp_menu_source_appearance_menu() {
        $wp_menu_source_enabled = (string) get_option('nexdirectory_frontend_sticky_nav_use_wp_menus', '0') === '1'
            || (string) get_option('nexdirectory_frontend_footer_use_wp_menus', '0') === '1';
        if (!$wp_menu_source_enabled || !current_user_can('edit_theme_options')) {
            return;
        }

        global $submenu;
        $appearance_submenus = isset($submenu['themes.php']) && is_array($submenu['themes.php']) ? (array) $submenu['themes.php'] : [];
        foreach ($appearance_submenus as $submenu_item) {
            if (!is_array($submenu_item)) {
                continue;
            }
            $submenu_slug = isset($submenu_item[2]) ? sanitize_key((string) $submenu_item[2]) : '';
            if ($submenu_slug === 'nav-menusphp' || $submenu_slug === 'nexdirectory-wp-menu-source') {
                return;
            }
        }

        add_submenu_page(
            'themes.php',
            __('Menus', 'nexdirectory'),
            __('Menus', 'nexdirectory'),
            'edit_theme_options',
            'nexdirectory-wp-menu-source',
            [$this, 'render_wp_menu_source_bridge_page']
        );
    }


    public function render_wp_menu_source_bridge_page() {
        if (!current_user_can('edit_theme_options')) {
            wp_die(esc_html__('Sorry, you are not allowed to access this page.', 'nexdirectory'));
        }

        $nav_menu_source_enabled = (string) get_option('nexdirectory_frontend_sticky_nav_use_wp_menus', '0') === '1';
        $footer_menu_source_enabled = (string) get_option('nexdirectory_frontend_footer_use_wp_menus', '0') === '1';
        $wp_menu_source_enabled = $nav_menu_source_enabled || $footer_menu_source_enabled;
        $settings_anchor = $nav_menu_source_enabled && !$footer_menu_source_enabled ? 'navbar' : 'footer';
        $settings_url = admin_url('admin.php?page=nexdirectory-settings#nexdirectory-tab-general--' . $settings_anchor);
        if (!$wp_menu_source_enabled) {
            wp_safe_redirect($settings_url);
            exit;
        }

        $classic_menus_url = admin_url('nav-menus.php');
        if (current_theme_supports('menus')) {
            wp_safe_redirect($classic_menus_url);
            exit;
        }

        $navigation_editor_url = '';
        if (function_exists('wp_is_block_theme') && wp_is_block_theme()) {
            $navigation_editor_url = admin_url('site-editor.php?path=%2Fnavigation');
            wp_safe_redirect($navigation_editor_url);
            exit;
        }

        echo '<div class="wrap">';
        echo '<h1>' . esc_html__('Menus', 'nexdirectory') . '</h1>';
        echo '<p>' . esc_html__('Menu management is enabled, but this theme does not currently expose a direct menu editor screen.', 'nexdirectory') . '</p>';
        echo '<p><a class="button button-primary" href="' . esc_url($classic_menus_url) . '">' . esc_html__('Open Classic Menus', 'nexdirectory') . '</a></p>';
        if ($navigation_editor_url !== '') {
            echo '<p><a class="button" href="' . esc_url($navigation_editor_url) . '">' . esc_html__('Open Navigation Editor', 'nexdirectory') . '</a></p>';
        }
        $brand_name = $this->get_admin_brand_name();
        /* translators: %s: Placeholder value. */
        $settings_label = sprintf(__('Back to %s Settings', 'nexdirectory'), $brand_name);
        echo '<p><a class="button" href="' . esc_url($settings_url) . '">' . esc_html($settings_label) . '</a></p>';
        echo '</div>';
    }


    public function register_settings_page() {
        $moderation_badge_count = $this->get_open_moderation_items_count();
        $badge_toggle = get_option('nexdirectory_notify_admin_pending_badge', null);
        if ($badge_toggle === null || $badge_toggle === false) {
            $badge_toggle = get_option('nexdirectory_enable_pending_admin_notifications', '1');
        }
        $show_pending_badge = (string) $badge_toggle === '1';
        $brand_name = $this->get_admin_brand_name();
        /* translators: %s: Filtered product name. */
        $menu_page_title = sprintf(__('%s Overview', 'nexdirectory'), $brand_name);
        /* translators: %s: Placeholder value. */
        $settings_menu_label = sprintf(__('%s Settings', 'nexdirectory'), $brand_name);
        $menu_title = $brand_name;
        if ($show_pending_badge && $moderation_badge_count > 0) {
            $menu_title .= ' <span class="awaiting-mod count-' . (int) $moderation_badge_count . '"><span class="pending-count">' . (int) $moderation_badge_count . '</span></span>';
        }
        $ui_labels = method_exists($this, 'get_site_profile_ui_labels') ? (array) $this->get_site_profile_ui_labels() : [];
        $plural_label = sanitize_text_field((string) ($ui_labels['plural'] ?? __('Events', 'nexdirectory')));
        if ($plural_label === '') {
            $plural_label = __('Events', 'nexdirectory');
        }
        $import_menu_label = __('Import Listing', 'nexdirectory');
        add_menu_page(
            $menu_page_title,
            $menu_title,
            'manage_options',
            'nexdirectory-dashboard',
            [$this, 'render_dashboard_page'],
            $this->get_white_label_menu_icon(),
            58
        );
        add_submenu_page(
            'nexdirectory-dashboard',
            $settings_menu_label,
            $settings_menu_label,
            'manage_options',
            'nexdirectory-settings',
            [$this, 'render_settings_page']
        );
        add_submenu_page(
            'nexdirectory-dashboard',
            __('User Management', 'nexdirectory'),
            __('User Management', 'nexdirectory'),
            'manage_options',
            'nexdirectory-user-management',
            [$this, 'render_admin_user_management_page']
        );
        $moderation_menu_title = __('Moderation Queue', 'nexdirectory');
        if ($show_pending_badge && $moderation_badge_count > 0) {
            $moderation_menu_title .= ' <span class="awaiting-mod count-' . (int) $moderation_badge_count . '"><span class="pending-count">' . (int) $moderation_badge_count . '</span></span>';
        }
        add_submenu_page(
            'nexdirectory-dashboard',
            __('Moderation Queue', 'nexdirectory'),
            $moderation_menu_title,
            'manage_options',
            'nexdirectory-moderation',
            [$this, 'render_admin_moderation_page']
        );
        add_submenu_page(
            'nexdirectory-dashboard',
            __('Onboarding Wizard', 'nexdirectory'),
            __('Onboarding Wizard', 'nexdirectory'),
            'manage_options',
            'nexdirectory-onboarding',
            [$this, 'render_onboarding_page']
        );
        add_submenu_page(
            'nexdirectory-dashboard',
            __('Diagnostics', 'nexdirectory'),
            __('Diagnostics', 'nexdirectory'),
            'manage_options',
            'nexdirectory-diagnostics',
            [$this, 'render_diagnostics_page']
        );
        add_submenu_page(
            'nexdirectory-dashboard',
            __('Free vs Pro', 'nexdirectory'),
            __('Free vs Pro', 'nexdirectory'),
            'manage_options',
            'nexdirectory-free-vs-pro',
            [$this, 'render_free_vs_pro_page']
        );
        add_submenu_page(
            'nexdirectory-dashboard',
            $import_menu_label,
            $import_menu_label,
            'manage_options',
            'nexdirectory-import',
            [$this, 'render_import_page']
        );
        add_submenu_page(
            'nexdirectory-dashboard',
            __('Change Listing Owner', 'nexdirectory'),
            __('Change Listing Owner', 'nexdirectory'),
            'manage_options',
            'nexdirectory-transfer-owner',
            [$this, 'render_transfer_owner_page']
        );
        remove_submenu_page('nexdirectory-dashboard', 'nexdirectory-transfer-owner');
    }

    /**
     * Adds useful, low-pressure links to the Plugins screen.
     *
     * @param array<int, string> $links Existing plugin action links.
     * @return array<int, string>
     */
    public function filter_plugin_action_links($links) {
        $links = is_array($links) ? $links : [];
        if (!current_user_can('manage_options')) {
            return $links;
        }

        array_unshift(
            $links,
            '<a href="' . esc_url(admin_url('admin.php?page=nexdirectory-settings')) . '">' . esc_html__('Settings', 'nexdirectory') . '</a>'
        );

        if (function_exists('nexdirectory_is_pro_addon_active') && nexdirectory_is_pro_addon_active()) {
            $links[] = '<a href="' . esc_url(admin_url('admin.php?page=nexdirectory-pro')) . '">' . esc_html__('Manage Pro', 'nexdirectory') . '</a>';
        } else {
            $links[] = '<a href="' . esc_url($this->get_free_pro_comparison_url()) . '">' . esc_html__('Compare Free & Pro', 'nexdirectory') . '</a>';
        }

        return $links;
    }

    /**
     * Returns the internal comparison-page URL, optionally focused on a row.
     *
     * @param string $feature Feature key.
     * @return string
     */
    public function get_free_pro_comparison_url($feature = '') {
        $url = admin_url('admin.php?page=nexdirectory-free-vs-pro');
        $feature = sanitize_key((string) $feature);
        if ($feature !== '') {
            $url .= '#nexdirectory-compare-' . $feature;
        }
        return $url;
    }

    /**
     * Provides a single factual source for the Free versus Pro table.
     *
     * @return array<int, array<string, mixed>>
     */
    public function get_free_pro_comparison_catalog() {
        $catalog = [
            [
                'key' => 'foundations',
                'label' => __('Build and publish', 'nexdirectory'),
                'description' => __('The essentials for launching and running a useful directory.', 'nexdirectory'),
                'features' => [
                    [
                        'key' => 'unlimited-publishing',
                        'label' => __('Listings and events', 'nexdirectory'),
                        'free' => __('Unlimited listings with roles, moderation, owner editing, and draft auto-save.', 'nexdirectory'),
                        'pro' => __('Everything included here; Pro is not needed to publish more listings.', 'nexdirectory'),
                        'free_included' => true,
                    ],
                    [
                        'key' => 'starter-blueprints',
                        'label' => __('Setup templates', 'nexdirectory'),
                        'free' => __('One-click setups for Events, Local Directory, Jobs, and Marketplace sites.', 'nexdirectory'),
                        'pro' => __('Tools for creating, duplicating, importing, and reusing custom setups.', 'nexdirectory'),
                        'free_included' => true,
                    ],
                    [
                        'key' => 'discovery',
                        'label' => __('Discovery and maps', 'nexdirectory'),
                        'free' => __('Archive, grid, map, calendar, distance, category, tag, and field filters.', 'nexdirectory'),
                        'pro' => __('Everything included here, with advanced global filter configuration.', 'nexdirectory'),
                        'free_included' => true,
                    ],
                    [
                        'key' => 'organizer-profiles',
                        'label' => __('Organizer profiles and privacy', 'nexdirectory'),
                        'free' => __('Organizer directory, profiles, contact privacy, and attendee-visibility controls.', 'nexdirectory'),
                        'pro' => __('Everything in Free, plus advanced team roles and collaboration permissions.', 'nexdirectory'),
                        'free_included' => true,
                    ],
                ],
            ],
            [
                'key' => 'automation',
                'label' => __('Automate the busywork', 'nexdirectory'),
                'description' => __('Reduce repeat admin work with reminders, summaries, and automation.', 'nexdirectory'),
                'features' => [
                    [
                        'key' => 'notification-automation',
                        'label' => __('Notifications', 'nexdirectory'),
                        'free' => __('Unified notification bell and essential transactional alerts.', 'nexdirectory'),
                        'pro' => __('Preview templates, send samples, brand messages, and configure reminder timing and sequences.', 'nexdirectory'),
                        'free_included' => true,
                    ],
                    [
                        'key' => 'needs-attention',
                        'label' => __('Needs Attention', 'nexdirectory'),
                        'free' => __('Live centre for moderation, claims, invitations, messages, RSVPs, and waitlists.', 'nexdirectory'),
                        'pro' => __('Scheduled administrator digests, previews, and sample delivery.', 'nexdirectory'),
                        'free_included' => true,
                    ],
                    [
                        'key' => 'visual-builder',
                        'label' => __('Visual field and filter builder', 'nexdirectory'),
                        'free' => __('Included fields, categories, tags, and filter settings.', 'nexdirectory'),
                        'pro' => __('Live visual builder for global fields, categories, archive filters, and map filters.', 'nexdirectory'),
                        'free_included' => false,
                        'free_status' => __('Core controls', 'nexdirectory'),
                    ],
                    [
                        'key' => 'ai-importer',
                        'label' => __('Chrome import enrichment', 'nexdirectory'),
                        'free' => __('Secure Connection Centre and dependable verbatim imports.', 'nexdirectory'),
                        'pro' => __('AI-assisted rewriting and enrichment for connected import drafts.', 'nexdirectory'),
                        'free_included' => false,
                        'free_status' => __('Verbatim import', 'nexdirectory'),
                    ],
                    [
                        'key' => 'ai-chatbot',
                        'label' => __('AI directory assistant', 'nexdirectory'),
                        'free' => __('Not included.', 'nexdirectory'),
                        'pro' => __('Visitor questions, semantic indexing, listing context, and guided recommendations.', 'nexdirectory'),
                        'free_included' => false,
                        'free_status' => __('Not included', 'nexdirectory'),
                    ],
                ],
            ],
            [
                'key' => 'growth',
                'label' => __('Revenue and insight', 'nexdirectory'),
                'description' => __('Add commercial workflows and understand how the platform is used.', 'nexdirectory'),
                'features' => [
                    [
                        'key' => 'analytics',
                        'label' => __('Analytics and exports', 'nexdirectory'),
                        'free' => __('Dashboard counts and basic activity records.', 'nexdirectory'),
                        'pro' => __('Platform and organizer trends, deeper metrics, and CSV exports.', 'nexdirectory'),
                        'free_included' => false,
                        'free_status' => __('Core stats', 'nexdirectory'),
                    ],
                    [
                        'key' => 'monetization',
                        'label' => __('Monetization', 'nexdirectory'),
                        'free' => __('Not included.', 'nexdirectory'),
                        'pro' => __('Paid tickets, listing boosts, packages, renewals, orders, Stripe, and PayPal workflows.', 'nexdirectory'),
                        'free_included' => false,
                        'free_status' => __('Not included', 'nexdirectory'),
                    ],
                    [
                        'key' => 'qr-checkin',
                        'label' => __('Browser QR check-in', 'nexdirectory'),
                        'free' => __('Not included.', 'nexdirectory'),
                        'pro' => __('Issue, scan, verify, and audit tickets with a manual fallback.', 'nexdirectory'),
                        'free_included' => false,
                        'free_status' => __('Not included', 'nexdirectory'),
                    ],
                ],
            ],
            [
                'key' => 'experience',
                'label' => __('Premium experiences', 'nexdirectory'),
                'description' => __('Create richer attendee, team, and client experiences.', 'nexdirectory'),
                'features' => [
                    [
                        'key' => 'subscribed-calendars',
                        'label' => __('Subscribed calendars', 'nexdirectory'),
                        'free' => __('Public site and organizer iCalendar feeds.', 'nexdirectory'),
                        'pro' => __('Revocable private personal attending-calendar feed.', 'nexdirectory'),
                        'free_included' => true,
                    ],
                    [
                        'key' => 'event-webapp',
                        'label' => __('Event Web App', 'nexdirectory'),
                        'free' => __('Not included.', 'nexdirectory'),
                        'pro' => __('Installable event experience with itinerary, chat, Q&A, polls, presence, maps, and app-style tools.', 'nexdirectory'),
                        'free_included' => false,
                        'free_status' => __('Not included', 'nexdirectory'),
                    ],
                    [
                        'key' => 'advanced-teams',
                        'label' => __('Advanced teams', 'nexdirectory'),
                        'free' => __('Included ownership, roles, and permission controls.', 'nexdirectory'),
                        'pro' => __('Scoped event roles, invitations with bell notifications, membership management, and collaborative workflows.', 'nexdirectory'),
                        'free_included' => false,
                        'free_status' => __('Core roles', 'nexdirectory'),
                    ],
                    [
                        'key' => 'white-label',
                        'label' => __('White label and agency tools', 'nexdirectory'),
                        'free' => __('Site branding and presentation settings.', 'nexdirectory'),
                        'pro' => __('Product branding, menu controls, support handoff, and configuration locks.', 'nexdirectory'),
                        'free_included' => false,
                        'free_status' => __('Core branding', 'nexdirectory'),
                    ],
                    [
                        'key' => 'search-push',
                        'label' => __('Search Push and developer hooks', 'nexdirectory'),
                        'free' => __('REST API support and extension connection points.', 'nexdirectory'),
                        'pro' => __('IndexNow automation, domain verification, completion hooks, and Pro webhooks.', 'nexdirectory'),
                        'free_included' => false,
                        'free_status' => __('Core API', 'nexdirectory'),
                    ],
                ],
            ],
        ];

        /**
         * Filters the informational Free versus Pro comparison catalogue.
         *
         * Extensions may refine wording or add truthful rows, but should not
         * attach premium runtime implementations to this Free-owned screen.
         *
         * @param array<int, array<string, mixed>> $catalog Comparison groups.
         */
        $filtered_catalog = apply_filters('nexdirectory_free_pro_comparison_rows', $catalog);
        return is_array($filtered_catalog) ? $filtered_catalog : $catalog;
    }

    /**
     * Renders the in-plugin plan comparison screen.
     */
    public function render_free_vs_pro_page() {
        if (!current_user_can('manage_options')) {
            return;
        }

        $template = plugin_dir_path(NEXDIRECTORY_PLUGIN_FILE) . 'templates/admin/free-vs-pro.php';
        if (!is_readable($template)) {
            wp_die(esc_html__('The Free vs Pro comparison is unavailable. Reinstall Nex Directory.', 'nexdirectory'));
        }

        require $template;
    }


    public function enqueue_admin_page_assets() {
        if (!is_admin()) {
            return;
        }
        $page = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin screen routing.
        if ($page === '' || strpos($page, 'nexdirectory-') !== 0) {
            return;
        }

        $plugin_path = plugin_dir_path(NEXDIRECTORY_PLUGIN_FILE);
        $plugin_url = plugin_dir_url(NEXDIRECTORY_PLUGIN_FILE);
        $admin_css_path = $plugin_path . 'assets/css/nexdirectory-admin.css';
        $admin_js_path = $plugin_path . 'assets/js/nexdirectory-admin.js';
        $admin_css_version = is_readable($admin_css_path)
            ? self::VERSION . '.' . (string) filemtime($admin_css_path)
            : self::VERSION;
        $admin_js_version = is_readable($admin_js_path)
            ? self::VERSION . '.' . (string) filemtime($admin_js_path)
            : self::VERSION;

        wp_enqueue_style(
            'nexdirectory-admin',
            $plugin_url . 'assets/css/nexdirectory-admin.css',
            [],
            $admin_css_version
        );
        wp_enqueue_script(
            'nexdirectory-admin',
            $plugin_url . 'assets/js/nexdirectory-admin.js',
            [],
            $admin_js_version,
            true
        );

        $frontend_component_pages = [
            'nexdirectory-settings',
            'nexdirectory-moderation',
            'nexdirectory-user-management',
            'nexdirectory-onboarding',
        ];
        if (in_array($page, $frontend_component_pages, true)) {
            $this->enqueue_assets();
        }
    }

    public function redirect_event_add_new_to_frontend_submit() {
        if (!is_admin()) {
            return;
        }

        $post_type = isset($_GET['post_type']) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            ? sanitize_key((string) wp_unslash($_GET['post_type'])) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            : 'post';
        if ($post_type !== self::POST_TYPE) {
            return;
        }

        if (isset($_GET['nexdirectory_wp_editor']) && sanitize_key((string) wp_unslash($_GET['nexdirectory_wp_editor'])) === '1') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            return;
        }

        $post_type_object = get_post_type_object(self::POST_TYPE);
        if (!$post_type_object || !is_object($post_type_object) || !isset($post_type_object->cap)) {
            return;
        }
        $create_capability = isset($post_type_object->cap->create_posts) && is_string($post_type_object->cap->create_posts)
            ? $post_type_object->cap->create_posts
            : ((isset($post_type_object->cap->edit_posts) && is_string($post_type_object->cap->edit_posts)) ? $post_type_object->cap->edit_posts : 'edit_posts');
        if (!current_user_can($create_capability)) {
            return;
        }

        $submit_page_url = trim((string) get_option('nexdirectory_page_submit_event_url', ''));
        if ($submit_page_url === '') {
            $submit_page_url = method_exists($this, 'get_profile_route_default_url')
                ? (string) $this->get_profile_route_default_url('submit')
                : (string) home_url('/submit/');
        }
        $submit_page_url = esc_url_raw($submit_page_url);
        if ($submit_page_url === '') {
            return;
        }

        $target_url = (string) nexdirectory_add_query_arg('nexdirectory_tab', 'submit', $submit_page_url);
        if (strpos($target_url, '#submit') === false) {
            $target_url .= '#submit';
        }

        wp_safe_redirect($target_url);
        exit;
    }


    public function register_elementor_category($elements_manager) {
        if (!is_object($elements_manager) || !method_exists($elements_manager, 'add_category')) {
            return;
        }
        $elements_manager->add_category('nexdirectory-widgets', [
            'title' => $this->get_admin_brand_name(),
            'icon' => 'fa fa-plug',
        ]);
    }


    public function register_elementor_widgets($widgets_manager) {
        if (!did_action('elementor/loaded')) {
            return;
        }
        $file = plugin_dir_path(NEXDIRECTORY_PLUGIN_FILE) . 'includes/elementor-widgets.php';
        if (file_exists($file)) {
            require_once $file;
        }
        if (function_exists('nexdirectory_register_elementor_widgets')) {
            nexdirectory_register_elementor_widgets($widgets_manager);
        }
    }


    public function register_settings() {
        register_setting('nexdirectory_settings_group', 'nexdirectory_google_maps_api_key', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_google_maps_api_key_option'],
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_pin_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_recaptcha_site_key', [
            'type' => 'string',
            'sanitize_callback' => 'nexdirectory_sanitize_text',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_recaptcha_secret_key', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_recaptcha_secret_key_option'],
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_recaptcha_version', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_recaptcha_version_option'],
            'default' => 'v3',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_recaptcha_show_submit', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_require_email_verification', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_enable_pending_admin_notifications', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_notify_admin_pending_badge', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_notify_admin_pending_email', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_notify_author_confirmation', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_notify_author_update', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_notify_author_cancellation', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_onboarding_complete', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smtp_enabled', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smtp_host', [
            'type' => 'string',
            'sanitize_callback' => 'nexdirectory_sanitize_text',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smtp_port', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_smtp_port_option'],
            'default' => 587,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smtp_encryption', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_smtp_encryption_option'],
            'default' => 'tls',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smtp_auth', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smtp_username', [
            'type' => 'string',
            'sanitize_callback' => 'nexdirectory_sanitize_text',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smtp_password', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_smtp_password_option'],
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smtp_from_email', [
            'type' => 'string',
            'sanitize_callback' => 'sanitize_email',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smtp_from_name', [
            'type' => 'string',
            'sanitize_callback' => 'nexdirectory_sanitize_text',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_login_redirect_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_page_events_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_page_events_map_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_page_events_calendar_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_page_submit_event_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_page_my_events_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_page_my_tickets_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_page_whos_attending_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_page_whats_new_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_page_account_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_page_login_signup_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_show_frontend_auth_links', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_enable_spa_hybrid', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_show_spa_feedback_badge', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_enable_frontend_sticky_nav', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_sticky_nav_behavior', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_sticky_nav_behavior_option'],
            'default' => 'sticky',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_sticky_nav_show_for_guests', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_sticky_nav_show_for_members', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_sticky_nav_include_auth_cta', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_sticky_nav_show_logo', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_sticky_nav_logo_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_sticky_nav_logo_link_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_sticky_nav_logo_size', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_sticky_nav_logo_size_option'],
            'default' => '66px',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_sticky_nav_custom_links', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_sticky_nav_custom_links_option'],
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_sticky_nav_use_wp_menus', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_sticky_nav_wp_menu_guest', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_nav_menu_id_option'],
            'default' => 0,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_sticky_nav_wp_menu_member', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_nav_menu_id_option'],
            'default' => 0,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_sticky_nav_wp_menu_admin', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_nav_menu_id_option'],
            'default' => 0,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_sticky_nav_wp_menu_roles', [
            'type' => 'array',
            'sanitize_callback' => [$this, 'sanitize_sticky_nav_role_menu_map_option'],
            'default' => [],
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_enable_frontend_footer', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_footer_show_logo', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_footer_logo_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_footer_logo_link_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_footer_use_wp_menus', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_footer_menu_count', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_footer_menu_count_option'],
            'default' => 3,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_footer_wp_menus', [
            'type' => 'array',
            'sanitize_callback' => [$this, 'sanitize_footer_wp_menu_list_option'],
            'default' => [],
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_footer_show_privacy_link', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_frontend_footer_bottom_text', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_footer_bottom_text_option'],
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_use_theme_defaults', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_accent_color', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_color_option'],
            'default' => '#FF5E14',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_accent_hover_color', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_color_option'],
            'default' => '#E04F0D',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_fallback_thumb_url', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_multi_image_urls_option'],
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_fallback_hero_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_favicon_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_home_meta_title', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_home_meta_title_option'],
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_home_meta_description', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_home_meta_description_option'],
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_font_family', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_font_family_option'],
            'default' => 'Inter, system-ui, -apple-system, sans-serif',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_display_font_family', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_font_family_option'],
            'default' => 'Oswald, Inter, sans-serif',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_font_size_base', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_size_option'],
            'default' => '18px',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_h2_size', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_size_option'],
            'default' => '1.75rem',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_h3_size', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_size_option'],
            'default' => '1.35rem',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_font_weight_body', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_font_weight_option'],
            'default' => '400',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_font_weight_heading', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_font_weight_option'],
            'default' => '700',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_spacing_unit', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_size_option'],
            'default' => '16px',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_spacing_scale', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_spacing_scale_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_radius_md', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_size_option'],
            'default' => '12px',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_radius_lg', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_size_option'],
            'default' => '24px',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_box_style', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_style_box_style_option'],
            'default' => 'default',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_button_style', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_style_button_style_option'],
            'default' => 'solid',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_button_radius_mode', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_style_button_radius_mode_option'],
            'default' => 'inherit',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_density_mode', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_style_density_mode_option'],
            'default' => 'comfortable',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_border_mode', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_style_border_mode_option'],
            'default' => 'subtle',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_shadow_mode', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_style_shadow_mode_option'],
            'default' => 'soft',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_content_width', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_style_content_width_option'],
            'default' => 'standard',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_style_input_style', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_style_input_style_option'],
            'default' => 'outline',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_enable_submit_autosave', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_autosave_interval_seconds', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_autosave_interval'],
            'default' => 60,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_enable_event_reporting', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_default_submit_show_nav', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_hide_theme_titles_on_plugin_pages', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_theme_override_all_plugin_pages', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_theme_override_full_width', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_theme_override_remove_padding', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_theme_override_hide_title', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_theme_override_hide_comments', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_theme_override_hide_featured_image', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_theme_override_hide_header', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_theme_override_reset_theme_styles', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_enable_map_auto_search', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_results_per_page', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_map_results_per_page_option'],
            'default' => 120,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_layout_default', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_map_layout_option'],
            'default' => 'three-column',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_page_side_padding', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_page_side_padding_option'],
            'default' => '10%',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_page_side_padding_mobile', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_page_side_padding_mobile_option'],
            'default' => '2%',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_nav_side_padding_desktop', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_nav_side_padding_desktop_option'],
            'default' => '0%',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_enable_smooth_scrolling', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smooth_scroll_profile', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_smooth_scroll_profile_option'],
            'default' => 'balanced',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smooth_scroll_touchpad_behavior', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_smooth_scroll_touchpad_behavior_option'],
            'default' => 'native',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smooth_scroll_wheel_multiplier', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_smooth_scroll_wheel_multiplier_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smooth_scroll_ease', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_smooth_scroll_ease_option'],
            'default' => '1.2',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smooth_scroll_max_step_px', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_smooth_scroll_max_step_option'],
            'default' => 960,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smooth_scroll_min_step_px', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_smooth_scroll_min_step_option'],
            'default' => '0.16',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_smooth_scroll_enable_on_touch', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_preloader_enabled', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_preloader_source', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_preloader_source_option'],
            'default' => 'svg',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_preloader_svg_markup', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_preloader_svg_option'],
            'default' => $this->get_default_preloader_svg_markup(),
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_preloader_image_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_preloader_background_color', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_preloader_background_color_option'],
            'default' => '#FFFFFF',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_preloader_logo_max_width', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_preloader_logo_max_width_option'],
            'default' => 420,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_preloader_min_duration_ms', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_preloader_min_duration_option'],
            'default' => 400,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_preloader_fade_duration_ms', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_preloader_fade_duration_option'],
            'default' => 420,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_preloader_show_on_initial', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_preloader_show_on_spa', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_preloader_show_only_home', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_preloader_show_once', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_preloader_disable_mobile', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_preloader_respect_motion', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_view_template', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_map_view_template_option'],
            'default' => 'default',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_focus_region', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_map_focus_region_option'],
            'default' => 'global',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_results_density', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_map_density_option'],
            'default' => 'comfortable',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_mobile_filter_mode', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_map_mobile_filter_mode_option'],
            'default' => 'drawer',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_provider', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_map_provider_option'],
            'default' => 'openstreetmap',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_style', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_map_style_option'],
            'default' => 'osm-standard',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_mapbox_access_token', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_mapbox_access_token_option'],
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_mapbox_style_id', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_mapbox_style_id_option'],
            'default' => 'mapbox/streets-v12',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_custom_tile_url', [
            'type' => 'string',
            'sanitize_callback' => 'nexdirectory_sanitize_text',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_custom_tile_attribution', [
            'type' => 'string',
            'sanitize_callback' => 'nexdirectory_sanitize_text',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_custom_tile_subdomains', [
            'type' => 'string',
            'sanitize_callback' => 'nexdirectory_sanitize_text',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_site_profile', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_site_profile_option'],
            'default' => 'events',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_platform_mode', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_platform_mode_option'],
            'default' => 'user_generated',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_event_hero_layout', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_hero_layout_option'],
            'default' => 'cinematic',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_event_hero_kicker_text', [
            'type' => 'string',
            'sanitize_callback' => 'nexdirectory_sanitize_text',
            'default' => 'Nex Directory',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_company_brand_name', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_company_brand_name_option'],
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_event_page_layout', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_event_page_layout_option'],
            'default' => 'modern',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_event_gallery_type', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_event_gallery_type_option'],
            'default' => 'default',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_enable_hero_parallax', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_filter_show_search', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_filter_show_type', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_filter_show_mode', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_filter_show_tag', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_filter_show_price', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_filter_show_official', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_filter_show_date', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_map_filter_show_distance', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_enable_moderation_ajax', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_use_action_scheduler', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_uninstall_remove_data', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_privacy_retention_days', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_privacy_retention_days_option'],
            'default' => 730,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_use_rich_editor', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_enable_duplicate_detection', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_official_classification_suffix', [
            'type' => 'string',
            'sanitize_callback' => 'nexdirectory_sanitize_text',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_upload_cover_max_kb', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_upload_limit_kb_option'],
            'default' => 500,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_upload_gallery_max_kb', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_upload_limit_kb_option'],
            'default' => 2000,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_upload_gallery_max_files', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_upload_files_count_option'],
            'default' => 10,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_upload_file_max_kb', [
            'type' => 'integer',
            'sanitize_callback' => [$this, 'sanitize_upload_limit_kb_option'],
            'default' => 2048,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_enable_event_seo_fields', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_enable_claim_listings', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_claim_require_admin_approval', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_claim_listing_page_url', [
            'type' => 'string',
            'sanitize_callback' => 'esc_url_raw',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_extension_enabled', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_extension_app_pass', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_extension_app_pass_option'],
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_extension_import_status', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_extension_import_status_option'],
            'default' => 'draft',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_extension_import_media_policy', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_extension_import_media_policy_option'],
            'default' => 'none',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_extension_import_content_mode', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_extension_import_content_mode_option'],
            'default' => 'verbatim',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_extension_notify_potential_owner_default', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_extension_import_author_id', [
            'type' => 'integer',
            'sanitize_callback' => 'absint',
            'default' => 0,
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_extension_import_mark_claimable', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_enable_autogenerated_disclaimer', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_autogenerated_disclaimer_text', [
            'type' => 'string',
            'sanitize_callback' => 'nexdirectory_sanitize_textarea',
            'default' => '',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_notify_admin_claim_email', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_notify_extension_potential_owner', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_user_edit_policy', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_user_edit_policy_option'],
            'default' => 'require_approval',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_auto_approve_edits_after_first_approval', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '0',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_role_event_submitter_can_submit', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_role_author_can_submit', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_role_editor_can_moderate', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        register_setting('nexdirectory_settings_group', 'nexdirectory_role_editor_manage_organizers', [
            'type' => 'string',
            'sanitize_callback' => [$this, 'sanitize_checkbox_option'],
            'default' => '1',
        ]);
        /**
         * Licensed add-ons register their own fields into the existing settings
         * group. Keeping this as an action avoids Free overwriting Pro options.
         */
        do_action('nexdirectory_register_extension_settings', 'nexdirectory_settings_group', $this);
    }


    public function sanitize_checkbox_option($value) {
        return ((string) $value === '1') ? '1' : '0';
    }

                public function sanitize_privacy_retention_days_option($value) {
        $days = absint((int) $value);
        if ($days < 0) {
            return 0;
        }
        if ($days > 3650) {
            return 3650;
        }
        return $days;
    }

    public function sanitize_home_meta_title_option($value) {
        $clean = sanitize_text_field((string) $value);
        if ($clean === '') {
            return '';
        }
        if (function_exists('mb_substr')) {
            $clean = mb_substr($clean, 0, 60);
        } else {
            $clean = substr($clean, 0, 60);
        }
        return trim((string) $clean);
    }

    public function sanitize_home_meta_description_option($value) {
        $clean = sanitize_text_field((string) $value);
        if ($clean === '') {
            return '';
        }
        if (function_exists('mb_substr')) {
            $clean = mb_substr($clean, 0, 158);
        } else {
            $clean = substr($clean, 0, 158);
        }
        return trim((string) $clean);
    }


    private function sanitize_secret_option_with_fallback($value, $option_key) {
        $value = is_scalar($value) ? (string) wp_unslash($value) : '';
        $value = str_replace("\0", '', $value);
        $value = wp_check_invalid_utf8($value);
        if ($value !== '' && preg_match('/^\*{6,}$/', $value)) {
            return (string) get_option($option_key, '');
        }
        if ($value !== '') {
            return $value;
        }
        return (string) get_option($option_key, '');
    }


    public function sanitize_google_maps_api_key_option($value) {
        return $this->sanitize_secret_option_with_fallback($value, 'nexdirectory_google_maps_api_key');
    }


    public function sanitize_recaptcha_secret_key_option($value) {
        return $this->sanitize_secret_option_with_fallback($value, 'nexdirectory_recaptcha_secret_key');
    }


    public function sanitize_smtp_password_option($value) {
        return $this->sanitize_secret_option_with_fallback($value, 'nexdirectory_smtp_password');
    }

    public function sanitize_mapbox_access_token_option($value) {
        return $this->sanitize_secret_option_with_fallback($value, 'nexdirectory_mapbox_access_token');
    }


    public function sanitize_site_profile_option($value) {
        $previous_value = (string) get_option('nexdirectory_site_profile', 'events');
        if (method_exists($this, 'normalize_site_profile_key')) {
            $value = (string) $this->normalize_site_profile_key($value);
            $previous_value = (string) $this->normalize_site_profile_key($previous_value);
        } else {
            $value = sanitize_key((string) $value);
            $previous_value = sanitize_key((string) $previous_value);
        }
        $catalog = method_exists($this, 'get_site_profile_catalog') ? (array) $this->get_site_profile_catalog() : [];
        if ($value === '' || !isset($catalog[$value])) {
            return 'events';
        }

        if ($previous_value !== '' && isset($catalog[$previous_value]) && $previous_value !== $value) {
            $this->maybe_sync_profile_page_urls($previous_value, $value);
        }
        return $value;
    }


    public function sanitize_platform_mode_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['user_generated', 'single_organisation'], true)
            ? $value
            : 'user_generated';
    }


    private function maybe_sync_profile_page_urls($from_profile_key, $to_profile_key) {
        if (!method_exists($this, 'get_profile_route_slugs') || !method_exists($this, 'get_profile_route_slug')) {
            return;
        }

        $from_profile_key = sanitize_key((string) $from_profile_key);
        $to_profile_key = sanitize_key((string) $to_profile_key);
        if ($from_profile_key === '' || $to_profile_key === '' || $from_profile_key === $to_profile_key) {
            return;
        }

        $route_map = [
            'nexdirectory_page_events_url' => 'events',
            'nexdirectory_page_events_map_url' => 'map',
            'nexdirectory_page_events_calendar_url' => 'calendar',
            'nexdirectory_page_submit_event_url' => 'submit',
            'nexdirectory_page_my_events_url' => 'my_items',
            'nexdirectory_page_my_tickets_url' => 'attending',
            'nexdirectory_page_whos_attending_url' => 'attendees',
            'nexdirectory_page_account_url' => 'account',
            'nexdirectory_page_login_signup_url' => 'auth',
            'nexdirectory_claim_listing_page_url' => 'claim',
        ];
        $route_map = (array) apply_filters(
            'nexdirectory_extension_profile_page_route_map',
            $route_map,
            $from_profile_key,
            $to_profile_key,
            $this
        );

        foreach ($route_map as $option_key => $route_key) {
            $current_url = esc_url_raw((string) get_option($option_key, ''));
            $from_slugs = array_values(array_unique(array_filter(array_map('nexdirectory_sanitize_title', (array) $this->get_profile_route_slugs($route_key, $from_profile_key)))));
            $to_slug = nexdirectory_sanitize_title((string) $this->get_profile_route_slug($route_key, $to_profile_key));
            if ($to_slug === '') {
                continue;
            }

            $current_slug = '';
            if ($current_url !== '') {
                $current_page_id = absint((int) url_to_postid($current_url));
                if ($current_page_id > 0) {
                    $page_uri = trim((string) get_page_uri($current_page_id), '/');
                    if ($page_uri !== '') {
                        $parts = array_values(array_filter(explode('/', $page_uri)));
                        if (!empty($parts)) {
                            $current_slug = nexdirectory_sanitize_title((string) end($parts));
                        }
                    }
                }
                if ($current_slug === '') {
                    $path = trim((string) wp_parse_url($current_url, PHP_URL_PATH), '/');
                    if ($path !== '') {
                        $parts = array_values(array_filter(explode('/', $path)));
                        if (!empty($parts)) {
                            $current_slug = nexdirectory_sanitize_title((string) end($parts));
                        }
                    }
                }
            }

            $is_mapped_to_old_mode = $current_url === '' || ($current_slug !== '' && in_array($current_slug, $from_slugs, true));
            if (!$is_mapped_to_old_mode) {
                continue;
            }

            $target_page = get_page_by_path($to_slug);
            if ($target_page instanceof WP_Post) {
                $target_url = get_permalink((int) $target_page->ID);
            } else {
                $target_url = $this->get_profile_route_default_url($route_key, $to_profile_key);
            }
            $target_url = esc_url_raw((string) $target_url);
            if ($target_url === '') {
                continue;
            }
            update_option($option_key, esc_url_raw($target_url), false);
        }
    }


    public function sanitize_company_brand_name_option($value) {
        $value = sanitize_text_field((string) $value);
        $value = trim((string) preg_replace('/\s+/u', ' ', $value));
        return function_exists('mb_substr') ? (string) mb_substr($value, 0, 120) : substr($value, 0, 120);
    }

    /**
     * Resolves the public site brand through a neutral extension filter.
     *
     * The legacy method name is retained for template and add-on compatibility;
     * Free does not read or own any White Label option.
     */
    private function get_white_label_brand_name() {
        $company_brand_name = sanitize_text_field((string) get_option('nexdirectory_company_brand_name', ''));
        $site_name = sanitize_text_field((string) get_option('blogname', get_bloginfo('name')));
        $brand_name = $company_brand_name !== '' ? $company_brand_name : $site_name;
        if ($brand_name === '') {
            $brand_name = self::PRODUCT_NAME;
        }
        return sanitize_text_field((string) apply_filters('nexdirectory_public_brand_name', $brand_name));
    }

    /**
     * Returns the product name used inside wp-admin.
     *
     * Directory/site branding belongs to the public experience. It must not
     * silently replace the plugin identity in the Plugins screen or admin menu.
     */
    private function get_admin_brand_name() {
        $brand_name = sanitize_text_field((string) apply_filters('nexdirectory_admin_brand_name', self::PRODUCT_NAME));
        return $brand_name !== '' ? $brand_name : self::PRODUCT_NAME;
    }


    /**
     * Resolves the stable admin menu icon through a neutral extension filter.
     */
    private function get_white_label_menu_icon() {
        $icon = sanitize_html_class((string) apply_filters('nexdirectory_admin_menu_icon', 'dashicons-admin-generic'));
        if ($icon === '' || strpos($icon, 'dashicons-') !== 0) {
            $icon = 'dashicons-admin-generic';
        }
        return $icon;
    }

    public function handle_reset_smooth_scroll_defaults() {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Not allowed.', 'nexdirectory'));
        }
        if (
            !isset($_POST['nexdirectory_reset_smooth_scroll_defaults_nonce'])
            || !wp_verify_nonce(sanitize_text_field((string) wp_unslash($_POST['nexdirectory_reset_smooth_scroll_defaults_nonce'])), 'nexdirectory_reset_smooth_scroll_defaults')
        ) {
            wp_die(esc_html__('Invalid request.', 'nexdirectory'));
        }

        $defaults = [
            'nexdirectory_enable_smooth_scrolling' => '1',
            'nexdirectory_smooth_scroll_profile' => 'balanced',
            'nexdirectory_smooth_scroll_touchpad_behavior' => 'native',
            'nexdirectory_smooth_scroll_wheel_multiplier' => '1',
            'nexdirectory_smooth_scroll_ease' => '1.2',
            'nexdirectory_smooth_scroll_max_step_px' => 960,
            'nexdirectory_smooth_scroll_min_step_px' => '0.16',
            'nexdirectory_smooth_scroll_enable_on_touch' => '0',
        ];
        foreach ($defaults as $option_key => $option_value) {
            update_option((string) $option_key, $option_value, false);
        }

        $this->redirect_with_message(
            admin_url('admin.php?page=nexdirectory-settings#nexdirectory-tab-advanced--display'),
            __('Smooth scrolling settings reset to Lenis defaults.', 'nexdirectory'),
            'success'
        );
    }    public function sanitize_multi_image_urls_option($value) {
        $raw = (string) $value;
        $rows = preg_split('/\r\n|\r|\n|,/', $raw);
        if (!is_array($rows)) {
            return '';
        }
        $urls = [];
        foreach ($rows as $row) {
            $url = esc_url_raw(trim((string) $row));
            if ($url === '') {
                continue;
            }
            $urls[] = $url;
        }
        $urls = array_values(array_unique($urls));
        return implode("\n", $urls);
    }


    public function sanitize_autosave_interval($value) {
        $v = absint($value);
        if ($v < 15) {
            $v = 15;
        }
        if ($v > 600) {
            $v = 600;
        }
        return $v;
    }


    public function sanitize_color_option($value) {
        $color = sanitize_hex_color((string) $value);
        return $color ?: '#FF5E14';
    }


    public function sanitize_font_family_option($value) {
        $value = preg_replace('/[^A-Za-z0-9,\-_\s\'"]+/', '', (string) $value);
        return trim((string) $value);
    }


    public function sanitize_size_option($value) {
        $value = strtolower(trim((string) $value));
        if (preg_match('/^(\d+(?:\.\d+)?)(px|rem|em)$/', $value, $matches)) {
            return (string) $matches[1] . (string) $matches[2];
        }
        if (is_numeric($value)) {
            return rtrim((string) $value, '.') . 'px';
        }
        return '18px';
    }


    public function sanitize_sticky_nav_logo_size_option($value) {
        $raw = strtolower(trim((string) $value));
        if ($raw === '') {
            return '66px';
        }
        if (preg_match('/^(\d+(?:\.\d+)?)(px|rem|em)?$/', $raw, $matches)) {
            $amount = (float) $matches[1];
            if ($amount < 18) {
                $amount = 18;
            }
            if ($amount > 96) {
                $amount = 96;
            }
            $unit = (isset($matches[2]) && $matches[2] !== '') ? (string) $matches[2] : 'px';
            $normalized = rtrim(rtrim(number_format($amount, 2, '.', ''), '0'), '.');
            return $normalized . $unit;
        }
        return '66px';
    }


    public function sanitize_font_weight_option($value) {
        $value = strtolower(trim((string) $value));
        if (preg_match('/^[1-9]00$/', $value)) {
            return $value;
        }
        return in_array($value, ['normal', 'bold', 'bolder', 'lighter'], true) ? $value : '400';
    }


    public function sanitize_spacing_scale_option($value) {
        $value = str_replace(',', '.', trim((string) $value));
        if (!is_numeric($value)) {
            return '1';
        }
        $float = (float) $value;
        if ($float < 0.6) {
            $float = 0.6;
        }
        if ($float > 2) {
            $float = 2;
        }
        if (abs($float - (float) ((int) $float)) < 0.000001) {
            return (string) ((int) $float);
        }
        $normalized = rtrim(rtrim(sprintf('%.6F', $float), '0'), '.');
        return $normalized !== '' ? $normalized : '1';
    }

    public function sanitize_style_box_style_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['default', 'gradient', 'bold', 'neumorphic'], true) ? $value : 'default';
    }

    public function sanitize_style_button_style_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['solid', 'outline', 'soft', 'dark'], true) ? $value : 'solid';
    }

    public function sanitize_style_button_radius_mode_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['inherit', 'pill', 'sharp'], true) ? $value : 'inherit';
    }

    public function sanitize_style_density_mode_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['compact', 'comfortable', 'loose'], true) ? $value : 'comfortable';
    }

    public function sanitize_style_border_mode_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['minimal', 'subtle', 'strong'], true) ? $value : 'subtle';
    }

    public function sanitize_style_shadow_mode_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['none', 'soft', 'medium', 'dramatic'], true) ? $value : 'soft';
    }

    public function sanitize_style_content_width_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['narrow', 'standard', 'wide', 'full'], true) ? $value : 'standard';
    }

    public function sanitize_style_input_style_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['outline', 'filled', 'underlined'], true) ? $value : 'outline';
    }


    public function sanitize_map_layout_option($value) {
        $value = sanitize_key((string) $value);
        if ($value === 'below') {
            $value = 'below-list';
        } elseif ($value === 'sidebar') {
            $value = 'three-column';
        }
        $allowed = ['three-column', 'below-list', 'below-grid', 'below-grid-large', 'grid-no-map', 'grid-no-map-large'];
        return in_array($value, $allowed, true) ? $value : 'three-column';
    }

    public function sanitize_map_view_template_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['default', 'triple'], true) ? $value : 'default';
    }

    private function get_map_focus_region_options() {
        return [
            'global' => __('Global (No Restriction)', 'nexdirectory'),
            'uk' => __('United Kingdom', 'nexdirectory'),
            'europe' => __('Europe', 'nexdirectory'),
            'america' => __('America', 'nexdirectory'),
            'north-america' => __('North America', 'nexdirectory'),
            'americas' => __('Americas (North + South)', 'nexdirectory'),
            'south-america' => __('South America', 'nexdirectory'),
            'africa' => __('Africa', 'nexdirectory'),
            'middle-east' => __('Middle East', 'nexdirectory'),
            'asia' => __('Asia', 'nexdirectory'),
            'oceania' => __('Oceania', 'nexdirectory'),
        ];
    }

    public function sanitize_map_focus_region_option($value) {
        $value = sanitize_key((string) $value);
        $allowed = array_keys((array) $this->get_map_focus_region_options());
        return in_array($value, $allowed, true) ? $value : 'global';
    }


    public function sanitize_map_density_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['comfortable', 'compact'], true) ? $value : 'comfortable';
    }


    public function sanitize_map_mobile_filter_mode_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['drawer', 'inline'], true) ? $value : 'drawer';
    }


    public function sanitize_page_side_padding_option($value) {
        $value = strtolower(trim((string) $value));
        if ($value === '') {
            return '10%';
        }

        if (is_numeric($value)) {
            $value .= '%';
        }

        if (!preg_match('/^(\d+(?:\.\d+)?)%$/', $value, $matches)) {
            return '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 . '%';
    }


    public function sanitize_page_side_padding_mobile_option($value) {
        $value = strtolower(trim((string) $value));
        if ($value === '') {
            // Empty means "inherit desktop side padding".
            return '';
        }
        return $this->sanitize_page_side_padding_option($value);
    }

    public function sanitize_nav_side_padding_desktop_option($value) {
        $value = strtolower(trim((string) $value));
        if ($value === '') {
            // Empty means "inherit desktop page padding".
            return '';
        }
        return $this->sanitize_page_side_padding_option($value);
    }

    public function sanitize_smooth_scroll_profile_option($value) {
        $value = sanitize_key((string) $value);
        $allowed = ['gentle', 'balanced', 'snappy', 'custom'];
        return in_array($value, $allowed, true) ? $value : 'balanced';
    }

    public function sanitize_smooth_scroll_touchpad_behavior_option($value) {
        $value = sanitize_key((string) $value);
        $allowed = ['native', 'assist', 'force'];
        return in_array($value, $allowed, true) ? $value : 'native';
    }

    public function sanitize_smooth_scroll_wheel_multiplier_option($value) {
        $value = str_replace(',', '.', trim((string) $value));
        if (!is_numeric($value)) {
            return '1';
        }
        $normalized = (float) $value;
        if ($normalized < 0.4) {
            $normalized = 0.4;
        } elseif ($normalized > 3) {
            $normalized = 3;
        }
        return rtrim(rtrim(number_format($normalized, 2, '.', ''), '0'), '.');
    }

    public function sanitize_smooth_scroll_ease_option($value) {
        $value = str_replace(',', '.', trim((string) $value));
        if (!is_numeric($value)) {
            return '1.2';
        }
        $normalized = (float) $value;
        if ($normalized < 0.4) {
            $normalized = 0.4;
        } elseif ($normalized > 3) {
            $normalized = 3;
        }
        return rtrim(rtrim(number_format($normalized, 3, '.', ''), '0'), '.');
    }

    public function sanitize_smooth_scroll_max_step_option($value) {
        $normalized = absint((int) $value);
        if ($normalized < 180) {
            $normalized = 180;
        } elseif ($normalized > 2400) {
            $normalized = 2400;
        }
        return $normalized;
    }

    public function sanitize_smooth_scroll_min_step_option($value) {
        $value = str_replace(',', '.', trim((string) $value));
        if (!is_numeric($value)) {
            return '0.16';
        }
        $normalized = (float) $value;
        if ($normalized < 0.02) {
            $normalized = 0.02;
        } elseif ($normalized > 2) {
            $normalized = 2;
        }
        return rtrim(rtrim(number_format($normalized, 3, '.', ''), '0'), '.');
    }

    private function get_default_preloader_svg_markup() {
        ob_start();
        ?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 200" width="100%" height="100%">
    <rect width="100%" height="100%" fill="#FFFFFF" />
    <text x="120" y="128" textLength="570" lengthAdjust="spacing" fill="#0A0A0A" style="font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;font-weight:850;font-size:66px;letter-spacing:0;">Nex Directory</text>
    <circle cy="120" cx="90" r="14" fill="#C3FF29" />
</svg>
        <?php
        return (string) ob_get_clean();
    }

    public function sanitize_preloader_source_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['svg', 'image'], true) ? $value : 'svg';
    }

    public function sanitize_preloader_svg_option($value) {
        $markup = trim((string) $value);
        if ($markup === '') {
            return $this->get_default_preloader_svg_markup();
        }

        $allowed = [
            'svg' => [
                'xmlns' => true,
                'viewbox' => true,
                'viewBox' => true,
                'width' => true,
                'height' => true,
                'fill' => true,
                'stroke' => true,
                'role' => true,
                'aria-hidden' => true,
                'preserveaspectratio' => true,
                'preserveAspectRatio' => true,
                'class' => true,
            ],
            'defs' => [],
            'style' => [],
            'clippath' => [
                'id' => true,
            ],
            'clipPath' => [
                'id' => true,
            ],
            'rect' => [
                'x' => true,
                'y' => true,
                'width' => true,
                'height' => true,
                'rx' => true,
                'fill' => true,
                'opacity' => true,
            ],
            'text' => [
                'x' => true,
                'y' => true,
                'textlength' => true,
                'textLength' => true,
                'lengthadjust' => true,
                'lengthAdjust' => true,
                'fill' => true,
                'clip-path' => true,
            ],
            'circle' => [
                'cx' => true,
                'cy' => true,
                'r' => true,
                'fill' => true,
                'opacity' => true,
            ],
            'animate' => [
                'attributename' => true,
                'attributeName' => true,
                'values' => true,
                'keytimes' => true,
                'keyTimes' => true,
                'calcmode' => true,
                'calcMode' => true,
                'keysplines' => true,
                'keySplines' => true,
                'dur' => true,
                'repeatcount' => true,
                'repeatCount' => true,
            ],
        ];

        $sanitized = wp_kses($markup, $allowed);
        if (trim($sanitized) === '') {
            return $this->get_default_preloader_svg_markup();
        }
        return $sanitized;
    }

    public function sanitize_preloader_background_color_option($value) {
        $color = sanitize_hex_color((string) $value);
        return is_string($color) && $color !== '' ? $color : '#FFFFFF';
    }

    public function sanitize_preloader_logo_max_width_option($value) {
        $size = absint((int) $value);
        if ($size < 120) {
            $size = 120;
        } elseif ($size > 900) {
            $size = 900;
        }
        return $size;
    }

    public function sanitize_preloader_min_duration_option($value) {
        $duration = absint((int) $value);
        if ($duration > 8000) {
            $duration = 8000;
        }
        return $duration;
    }

    public function sanitize_preloader_fade_duration_option($value) {
        $duration = absint((int) $value);
        if ($duration < 80) {
            $duration = 80;
        } elseif ($duration > 2000) {
            $duration = 2000;
        }
        return $duration;
    }


    public function sanitize_map_provider_option($value) {
        $value = sanitize_key((string) $value);
        $allowed = ['openstreetmap', 'mapbox', 'google'];
        return in_array($value, $allowed, true) ? $value : 'openstreetmap';
    }

    private function get_mapbox_style_id_options() {
        return [
            'mapbox/streets-v12' => __('Streets v12', 'nexdirectory'),
            'mapbox/outdoors-v12' => __('Outdoors v12', 'nexdirectory'),
            'mapbox/satellite-streets-v12' => __('Satellite Streets v12', 'nexdirectory'),
            'mapbox/light-v11' => __('Light v11', 'nexdirectory'),
            'mapbox/dark-v11' => __('Dark v11', 'nexdirectory'),
            'mapbox/navigation-day-v1' => __('Navigation Day v1', 'nexdirectory'),
            'mapbox/navigation-night-v1' => __('Navigation Night v1', 'nexdirectory'),
        ];
    }

    public function sanitize_mapbox_style_id_option($value) {
        $value = sanitize_text_field((string) $value);
        $allowed = array_keys((array) $this->get_mapbox_style_id_options());
        if (in_array($value, $allowed, true)) {
            return $value;
        }
        return 'mapbox/streets-v12';
    }


    public function sanitize_map_style_option($value) {
        $value = sanitize_key((string) $value);
        $allowed = [
            'osm-standard',
            'osm-hot',
            'carto-light',
            'carto-dark',
            'topo',
            'mapbox-streets',
            'mapbox-outdoors',
            'mapbox-satellite',
            'google-roadmap',
            'google-satellite',
            'custom',
        ];
        return in_array($value, $allowed, true) ? $value : 'osm-standard';
    }


    public function sanitize_recaptcha_version_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['v3', 'v2'], true) ? $value : 'v3';
    }


    private function get_settings_email_health() {
        $smtp_enabled = (string) get_option('nexdirectory_smtp_enabled', '0') === '1';
        $smtp_host = trim((string) get_option('nexdirectory_smtp_host', ''));
        $smtp_from_email = sanitize_email((string) get_option('nexdirectory_smtp_from_email', ''));

        if (!$smtp_enabled) {
            return [
                'connected' => false,
                'summary' => __('SMTP is disabled. Enable SMTP and add provider credentials to connect email delivery.', 'nexdirectory'),
            ];
        }
        if ($smtp_host === '') {
            return [
                'connected' => false,
                'summary' => __('SMTP is enabled but host is missing.', 'nexdirectory'),
            ];
        }
        if ($smtp_from_email !== '' && !is_email($smtp_from_email)) {
            return [
                'connected' => false,
                'summary' => __('SMTP From Email is invalid.', 'nexdirectory'),
            ];
        }

        return [
            'connected' => true,
            /* translators: %s: Placeholder value. */
            'summary' => sprintf(__('SMTP is connected to %s.', 'nexdirectory'), $smtp_host),
        ];
    }

    private function get_settings_map_health() {
        $provider = sanitize_key((string) get_option('nexdirectory_map_provider', 'openstreetmap'));
        if (!in_array($provider, ['openstreetmap', 'mapbox', 'google'], true)) {
            $provider = 'openstreetmap';
        }

        $style_mode = sanitize_key((string) get_option('nexdirectory_map_style', 'osm-standard'));
        $provider_label = [
            'openstreetmap' => __('OpenStreetMap', 'nexdirectory'),
            'mapbox' => __('Mapbox', 'nexdirectory'),
            'google' => __('Google Maps', 'nexdirectory'),
        ];

        if ($provider === 'mapbox') {
            $token = trim((string) get_option('nexdirectory_mapbox_access_token', ''));
            if ($token === '') {
                return [
                    'connected' => false,
                    'summary' => __('Mapbox is selected but access token is missing.', 'nexdirectory'),
                ];
            }
        } elseif ($provider === 'google') {
            $google_key = trim((string) get_option('nexdirectory_google_maps_api_key', ''));
            if ($google_key === '') {
                return [
                    'connected' => false,
                    'summary' => __('Google Maps is selected but API key is missing.', 'nexdirectory'),
                ];
            }
        }

        if ($style_mode === 'custom') {
            $custom_tile_url = trim((string) get_option('nexdirectory_map_custom_tile_url', ''));
            if ($custom_tile_url === '') {
                return [
                    'connected' => false,
                    'summary' => __('Custom tile mode is selected but tile URL template is missing.', 'nexdirectory'),
                ];
            }
        }

        $active_provider_label = (string) ($provider_label[$provider] ?? __('Map provider', 'nexdirectory'));
        return [
            'connected' => true,
            /* translators: %s: Placeholder value. */
            'summary' => sprintf(__('%s provider is configured.', 'nexdirectory'), $active_provider_label),
        ];
    }

    private function get_settings_captcha_health() {
        $site_key = trim((string) get_option('nexdirectory_recaptcha_site_key', ''));
        $secret_key = trim((string) get_option('nexdirectory_recaptcha_secret_key', ''));
        $version = sanitize_key((string) get_option('nexdirectory_recaptcha_version', 'v3'));
        if (!in_array($version, ['v3', 'v2'], true)) {
            $version = 'v3';
        }

        if ($site_key === '' || $secret_key === '') {
            return [
                'connected' => false,
                'summary' => __('reCAPTCHA site key or secret key is missing.', 'nexdirectory'),
            ];
        }

        return [
            'connected' => true,
            /* translators: %s: Placeholder value. */
            'summary' => sprintf(__('reCAPTCHA %s keys are configured.', 'nexdirectory'), strtoupper($version)),
        ];
    }

    private function get_settings_route_targets($site_profile_key = '') {
        $profile_key = sanitize_key((string) $site_profile_key);
        if ($profile_key === '' && method_exists($this, 'get_site_profile_key')) {
            $profile_key = sanitize_key((string) $this->get_site_profile_key());
        }
        if ($profile_key === '') {
            $profile_key = 'events';
        }

        $build_route = function ($option_key, $route_key, $label) use ($profile_key) {
            $fallback = '';
            if (method_exists($this, 'get_profile_route_default_url')) {
                $fallback = esc_url_raw((string) $this->get_profile_route_default_url($route_key, $profile_key));
            }
            return [
                'option' => (string) $option_key,
                'label' => (string) $label,
                'fallback' => (string) $fallback,
            ];
        };

        $routes = [
            $build_route('nexdirectory_page_events_url', 'events', __('Listings Page', 'nexdirectory')),
            $build_route('nexdirectory_page_events_map_url', 'map', __('Map Page', 'nexdirectory')),
            $build_route('nexdirectory_page_events_calendar_url', 'calendar', __('Calendar Page', 'nexdirectory')),
            $build_route('nexdirectory_page_submit_event_url', 'submit', __('Submit Listing Page', 'nexdirectory')),
            $build_route('nexdirectory_page_my_events_url', 'my_items', __('My Listings Page', 'nexdirectory')),
            $build_route('nexdirectory_page_my_tickets_url', 'attending', __('Saved/Attending Page', 'nexdirectory')),
            $build_route('nexdirectory_page_whos_attending_url', 'attendees', __('Who\'s Attending Page', 'nexdirectory')),
            [
                'option' => 'nexdirectory_page_whats_new_url',
                'label' => __('What\'s New Page', 'nexdirectory'),
                'fallback' => esc_url_raw((string) home_url('/whats-new/')),
            ],
            $build_route('nexdirectory_page_account_url', 'account', __('Account Page', 'nexdirectory')),
            $build_route('nexdirectory_page_login_signup_url', 'auth', __('Login/Signup Page', 'nexdirectory')),
            $build_route('nexdirectory_claim_listing_page_url', 'claim', __('Claim Listing Page', 'nexdirectory')),
        ];
        return (array) apply_filters(
            'nexdirectory_extension_settings_route_targets',
            $routes,
            $profile_key,
            $this
        );
    }

    private function get_settings_routing_health($site_profile_key = '') {
        $routes = $this->get_settings_route_targets($site_profile_key);
        $site_host = strtolower((string) wp_parse_url(home_url('/'), PHP_URL_HOST));
        $connected_count = 0;
        $issues = [];

        foreach ($routes as $route) {
            $option_key = sanitize_key((string) ($route['option'] ?? ''));
            if ($option_key === '') {
                continue;
            }
            $label = sanitize_text_field((string) ($route['label'] ?? $option_key));
            $raw_url = trim((string) get_option($option_key, ''));
            if ($raw_url === '') {
                $raw_url = trim((string) ($route['fallback'] ?? ''));
            }
            if ($raw_url === '') {
                /* translators: %s: Placeholder value. */
                $issues[] = sprintf(__('%s has no URL configured.', 'nexdirectory'), $label);
                continue;
            }
            if (strpos($raw_url, '/') === 0) {
                $raw_url = home_url($raw_url);
            }
            $url = esc_url_raw($raw_url);
            if ($url === '') {
                /* translators: %s: Placeholder value. */
                $issues[] = sprintf(__('%s URL is invalid.', 'nexdirectory'), $label);
                continue;
            }
            $path = trim((string) wp_parse_url($url, PHP_URL_PATH));
            if ($path === '' || $path === '/') {
                /* translators: %s: Placeholder value. */
                $issues[] = sprintf(__('%s points to the site root instead of a dedicated page.', 'nexdirectory'), $label);
                continue;
            }
            $route_host = strtolower((string) wp_parse_url($url, PHP_URL_HOST));
            if ($route_host !== '' && $site_host !== '' && $route_host !== $site_host) {
                /* translators: %s: Placeholder value. */
                $issues[] = sprintf(__('%s points to an external domain.', 'nexdirectory'), $label);
                continue;
            }

            $post_id = absint((int) url_to_postid($url));
            if ($post_id <= 0) {
                /* translators: %s: Placeholder value. */
                $issues[] = sprintf(__('%s is not linked to a WordPress page.', 'nexdirectory'), $label);
                continue;
            }
            $post_status = (string) get_post_status($post_id);
            if ($post_status === '' || in_array($post_status, ['trash', 'auto-draft'], true)) {
                /* translators: %s: Placeholder value. */
                $issues[] = sprintf(__('%s points to an unavailable page status.', 'nexdirectory'), $label);
                continue;
            }
            $connected_count++;
        }

        $total_count = count($routes);
        $is_connected = $total_count > 0 && $connected_count === $total_count;
        if ($is_connected) {
            /* translators: %d: Placeholder value. */
            $summary = sprintf(__('All %d frontend routes are connected to valid pages.', 'nexdirectory'), $total_count);
        } else {
            $summary = sprintf(
                /* translators: 1: Placeholder value 1, 2: Placeholder value 2, 3: Placeholder value 3. */
                __('%1$d of %2$d routes are connected. %3$s', 'nexdirectory'),
                $connected_count,
                $total_count,
                (string) ($issues[0] ?? __('Review routing URLs and generate missing pages if needed.', 'nexdirectory'))
            );
        }

        return [
            'connected' => $is_connected,
            'summary' => $summary,
            'connected_count' => $connected_count,
            'total_count' => $total_count,
            'issues' => $issues,
        ];
    }

    private function sync_route_options_to_existing_pages($profile_key = '') {
        if (!method_exists($this, 'get_profile_route_slug') || !method_exists($this, 'get_profile_route_slugs') || !method_exists($this, 'get_profile_route_default_url')) {
            return;
        }

        $profile_key = sanitize_key((string) $profile_key);
        if ($profile_key === '' && method_exists($this, 'get_site_profile_key')) {
            $profile_key = sanitize_key((string) $this->get_site_profile_key());
        }
        if ($profile_key === '') {
            $profile_key = 'events';
        }

        $route_map = [
            'nexdirectory_page_events_url' => 'events',
            'nexdirectory_page_events_map_url' => 'map',
            'nexdirectory_page_events_calendar_url' => 'calendar',
            'nexdirectory_page_submit_event_url' => 'submit',
            'nexdirectory_page_my_events_url' => 'my_items',
            'nexdirectory_page_my_tickets_url' => 'attending',
            'nexdirectory_page_whos_attending_url' => 'attendees',
            'nexdirectory_page_account_url' => 'account',
            'nexdirectory_page_login_signup_url' => 'auth',
            'nexdirectory_claim_listing_page_url' => 'claim',
        ];
        $route_map = (array) apply_filters(
            'nexdirectory_extension_admin_route_map',
            $route_map,
            $profile_key,
            $this
        );

        foreach ($route_map as $option_key => $route_key) {
            $current_url = esc_url_raw((string) get_option($option_key, ''));
            $is_current_valid = false;
            if ($current_url !== '') {
                $path = trim((string) wp_parse_url($current_url, PHP_URL_PATH));
                $is_root_url = ($path === '' || $path === '/');
                if (!$is_root_url) {
                    $is_current_valid = absint((int) url_to_postid($current_url)) > 0;
                }
            }

            if ($is_current_valid) {
                continue;
            }

            $default_slug = nexdirectory_sanitize_title((string) $this->get_profile_route_slug($route_key, $profile_key));
            $candidate_slugs = array_values(array_unique(array_filter(array_map('nexdirectory_sanitize_title',
                array_merge([$default_slug], (array) $this->get_profile_route_slugs($route_key, $profile_key))
            ))));
            $target_url = '';
            foreach ($candidate_slugs as $candidate_slug) {
                $page = get_page_by_path($candidate_slug);
                if ($page instanceof WP_Post) {
                    $target_url = esc_url_raw((string) get_permalink((int) $page->ID));
                    break;
                }
            }
            if ($target_url === '') {
                $shortcode_map = [
                    'events' => 'nexdirectory_listings_archive',
                    'map' => 'nexdirectory_listings_map',
                    'calendar' => 'nexdirectory_listings_calendar',
                    'submit' => 'nexdirectory_submit',
                    'my_items' => 'nexdirectory_my_listings',
                    'attending' => 'nexdirectory_saved_listings',
                    'attendees' => 'nexdirectory_whos_attending',
                    'account' => 'nexdirectory_account',
                    'auth' => 'nexdirectory_auth',
                    'claim' => 'nexdirectory_claim_listing',
                ];
                $shortcode_map = (array) apply_filters(
                    'nexdirectory_extension_admin_route_shortcode_map',
                    $shortcode_map,
                    $profile_key,
                    $this
                );
                $shortcode_tag = sanitize_key((string) ($shortcode_map[$route_key] ?? ''));
                if ($shortcode_tag !== '') {
                    global $wpdb;
                    if ($wpdb instanceof 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
                        )); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                        if ($shortcode_page_id > 0) {
                            $shortcode_url = get_permalink($shortcode_page_id);
                            if (is_string($shortcode_url) && $shortcode_url !== '') {
                                $target_url = esc_url_raw((string) $shortcode_url);
                            }
                        }
                    }
                }
            }
            if ($target_url === '') {
                $target_url = esc_url_raw((string) $this->get_profile_route_default_url($route_key, $profile_key));
            }
            if ($target_url !== '') {
                $target_path = trim((string) wp_parse_url($target_url, PHP_URL_PATH));
                if ($target_path === '' || $target_path === '/') {
                    $target_url = '';
                }
            }
            if ($target_url !== '') {
                update_option($option_key, $target_url, false);
            }
        }

        $whats_new_page = get_page_by_path('whats-new');
        $whats_new_url = $whats_new_page instanceof WP_Post
            ? esc_url_raw((string) get_permalink((int) $whats_new_page->ID))
            : esc_url_raw(home_url('/whats-new/'));
        if ($whats_new_url !== '') {
            update_option('nexdirectory_page_whats_new_url', $whats_new_url, false);
        }
    }

    private function run_settings_map_connection_test() {
        $health = $this->get_settings_map_health();
        if (empty($health['connected'])) {
            return [
                'ok' => false,
                'message' => sanitize_text_field((string) ($health['summary'] ?? __('Map configuration is incomplete.', 'nexdirectory'))),
            ];
        }

        $provider = sanitize_key((string) get_option('nexdirectory_map_provider', 'openstreetmap'));
        if (!in_array($provider, ['openstreetmap', 'mapbox', 'google'], true)) {
            $provider = 'openstreetmap';
        }

        $headers = [
            'Accept' => 'application/json',
            'User-Agent' => 'NexDirectory/' . self::VERSION . ' (' . home_url('/') . ')',
        ];
        $provider_message = '';

        if ($provider === 'google') {
            $google_key = trim((string) get_option('nexdirectory_google_maps_api_key', ''));
            $google_url = nexdirectory_add_query_arg([
                'address' => 'London',
                'key' => $google_key,
            ], 'https://maps.googleapis.com/maps/api/geocode/json');
            $google_response = wp_remote_get($google_url, [
                'timeout' => 12,
                'headers' => $headers,
            ]);
            if (is_wp_error($google_response)) {
                return [
                    'ok' => false,
                    /* translators: %s: Placeholder value. */
                    'message' => sprintf(__('Google Maps request failed: %s', 'nexdirectory'), $google_response->get_error_message()),
                ];
            }
            $payload = json_decode((string) wp_remote_retrieve_body($google_response), true);
            $status = sanitize_key((string) ($payload['status'] ?? ''));
            if (!in_array($status, ['ok', 'zero_results'], true)) {
                $error_message = sanitize_text_field((string) ($payload['error_message'] ?? $status));
                if ($error_message === '') {
                    $error_message = __('Unknown Google Maps error.', 'nexdirectory');
                }
                return [
                    'ok' => false,
                    /* translators: %s: Placeholder value. */
                    'message' => sprintf(__('Google Maps test failed: %s', 'nexdirectory'), $error_message),
                ];
            }
            $provider_message = __('Google Maps API responded successfully.', 'nexdirectory');
        } elseif ($provider === 'mapbox') {
            $token = trim((string) get_option('nexdirectory_mapbox_access_token', ''));
            $style_id = $this->sanitize_mapbox_style_id_option((string) get_option('nexdirectory_mapbox_style_id', 'mapbox/streets-v12'));
            $style_segments = array_filter(array_map('trim', explode('/', $style_id)), static function ($segment) {
                return $segment !== '';
            });
            if (empty($style_segments)) {
                $style_segments = ['mapbox', 'streets-v12'];
            }
            $style_path = implode('/', array_map('rawurlencode', $style_segments));
            $style_url = nexdirectory_add_query_arg([
                'access_token' => $token,
            ], 'https://api.mapbox.com/styles/v1/' . $style_path);
            $mapbox_response = wp_remote_get($style_url, [
                'timeout' => 12,
                'headers' => $headers,
            ]);
            if (is_wp_error($mapbox_response)) {
                return [
                    'ok' => false,
                    /* translators: %s: Placeholder value. */
                    'message' => sprintf(__('Mapbox request failed: %s', 'nexdirectory'), $mapbox_response->get_error_message()),
                ];
            }
            $status_code = (int) wp_remote_retrieve_response_code($mapbox_response);
            if ($status_code < 200 || $status_code >= 300) {
                $payload = json_decode((string) wp_remote_retrieve_body($mapbox_response), true);
                $error_message = sanitize_text_field((string) ($payload['message'] ?? ''));
                if ($error_message === '') {
                    /* translators: %d: Placeholder value. */
                    $error_message = sprintf(__('HTTP %d', 'nexdirectory'), $status_code);
                }
                return [
                    'ok' => false,
                    /* translators: %s: Placeholder value. */
                    'message' => sprintf(__('Mapbox test failed: %s', 'nexdirectory'), $error_message),
                ];
            }
            $provider_message = __('Mapbox API responded successfully.', 'nexdirectory');
        } else {
            $osm_url = nexdirectory_add_query_arg([
                'format' => 'json',
                'limit' => 1,
                'q' => 'London',
            ], 'https://nominatim.openstreetmap.org/search');
            $osm_response = wp_remote_get($osm_url, [
                'timeout' => 12,
                'headers' => $headers,
            ]);
            if (is_wp_error($osm_response)) {
                return [
                    'ok' => false,
                    /* translators: %s: Placeholder value. */
                    'message' => sprintf(__('OpenStreetMap request failed: %s', 'nexdirectory'), $osm_response->get_error_message()),
                ];
            }
            $status_code = (int) wp_remote_retrieve_response_code($osm_response);
            if ($status_code < 200 || $status_code >= 300) {
                return [
                    'ok' => false,
                    /* translators: %d: Placeholder value. */
                    'message' => sprintf(__('OpenStreetMap test failed with HTTP %d.', 'nexdirectory'), $status_code),
                ];
            }
            $payload = json_decode((string) wp_remote_retrieve_body($osm_response), true);
            if (!is_array($payload)) {
                return [
                    'ok' => false,
                    'message' => __('OpenStreetMap returned an unexpected response payload.', 'nexdirectory'),
                ];
            }
            $provider_message = __('OpenStreetMap endpoint responded successfully.', 'nexdirectory');
        }

        if (class_exists('WP_REST_Request')) {
            $rest_request = new WP_REST_Request('GET', '/nexdirectory/v1/map-events');
            $rest_request->set_query_params(['limit' => 1]);
            $rest_response = rest_do_request($rest_request);
            if ($rest_response instanceof WP_Error) {
                return [
                    'ok' => false,
                    /* translators: %s: Placeholder value. */
                    'message' => sprintf(__('Map REST endpoint failed: %s', 'nexdirectory'), $rest_response->get_error_message()),
                ];
            }
            $rest_status = (int) $rest_response->get_status();
            if ($rest_status < 200 || $rest_status >= 300) {
                return [
                    'ok' => false,
                    /* translators: %d: Placeholder value. */
                    'message' => sprintf(__('Map REST endpoint returned HTTP %d.', 'nexdirectory'), $rest_status),
                ];
            }
        }

        return [
            'ok' => true,
            /* translators: %s: Placeholder value. */
            'message' => sprintf(__('Map connection test passed. %s', 'nexdirectory'), $provider_message),
        ];
    }

    private function run_settings_captcha_connection_test() {
        $health = $this->get_settings_captcha_health();
        if (empty($health['connected'])) {
            return [
                'ok' => false,
                'message' => sanitize_text_field((string) ($health['summary'] ?? __('reCAPTCHA keys are incomplete.', 'nexdirectory'))),
            ];
        }

        $secret_key = trim((string) get_option('nexdirectory_recaptcha_secret_key', ''));
        $siteverify_response = wp_remote_post('https://www.google.com/recaptcha/api/siteverify', [
            'timeout' => 12,
            'body' => [
                'secret' => $secret_key,
                'response' => 'settings_connection_test_token',
                'remoteip' => sanitize_text_field((string) wp_unslash($_SERVER['REMOTE_ADDR'] ?? '')),
            ],
        ]);
        if (is_wp_error($siteverify_response)) {
            return [
                'ok' => false,
                /* translators: %s: Placeholder value. */
                'message' => sprintf(__('reCAPTCHA test request failed: %s', 'nexdirectory'), $siteverify_response->get_error_message()),
            ];
        }

        $status_code = (int) wp_remote_retrieve_response_code($siteverify_response);
        if ($status_code < 200 || $status_code >= 300) {
            return [
                'ok' => false,
                /* translators: %d: Placeholder value. */
                'message' => sprintf(__('reCAPTCHA test failed with HTTP %d.', 'nexdirectory'), $status_code),
            ];
        }

        $payload = json_decode((string) wp_remote_retrieve_body($siteverify_response), true);
        if (!is_array($payload)) {
            return [
                'ok' => false,
                'message' => __('reCAPTCHA returned an unexpected response payload.', 'nexdirectory'),
            ];
        }

        if (!empty($payload['success'])) {
            return [
                'ok' => true,
                'message' => __('reCAPTCHA connection test passed.', 'nexdirectory'),
            ];
        }

        $error_codes = [];
        if (!empty($payload['error-codes']) && is_array($payload['error-codes'])) {
            foreach ((array) $payload['error-codes'] as $error_code) {
                $code = sanitize_key((string) $error_code);
                if ($code !== '') {
                    $error_codes[] = $code;
                }
            }
        }

        if (in_array('invalid-input-secret', $error_codes, true) || in_array('missing-input-secret', $error_codes, true)) {
            return [
                'ok' => false,
                'message' => __('reCAPTCHA secret key is invalid or missing.', 'nexdirectory'),
            ];
        }

        if (in_array('invalid-input-response', $error_codes, true) || in_array('missing-input-response', $error_codes, true)) {
            return [
                'ok' => true,
                'message' => __('reCAPTCHA keys are reachable. Dummy token was rejected as expected.', 'nexdirectory'),
            ];
        }

        return [
            'ok' => false,
            'message' => sprintf(
                /* translators: %s: Placeholder value. */
                __('reCAPTCHA test returned errors: %s', 'nexdirectory'),
                !empty($error_codes) ? implode(', ', $error_codes) : __('unknown', 'nexdirectory')
            ),
        ];
    }


    public function sanitize_user_edit_policy_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['allow', 'require_approval', 'disallow'], true) ? $value : 'require_approval';
    }


    public function sanitize_extension_import_status_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['draft', 'publish'], true) ? $value : 'draft';
    }


    public function sanitize_extension_import_media_policy_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['none', 'images', 'images_video', 'video'], true) ? $value : 'none';
    }

    public function sanitize_extension_import_content_mode_option($value) {
        $value = sanitize_key((string) $value);
        $ai_importer_enabled = function_exists('nexdirectory_is_pro_feature_enabled')
            && nexdirectory_is_pro_feature_enabled('ai_importer');
        if (!$ai_importer_enabled) {
            return 'verbatim';
        }

        return in_array($value, ['rewrite', 'verbatim'], true) ? $value : 'rewrite';
    }


    public function sanitize_sticky_nav_behavior_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['sticky', 'static', 'smart'], true) ? $value : 'sticky';
    }


    public function sanitize_sticky_nav_custom_links_option($value) {
        $lines = preg_split('/\r\n|\r|\n/', (string) $value);
        if (!is_array($lines)) {
            $lines = [];
        }

        $normalized = [];
        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 = sanitize_text_field((string) ($parts[3] ?? ''));
            $group = trim((string) str_replace('|', ' ', $group));
            if ($group === '') {
                $group = 'More';
            }
            if ($audience === 'loggedin' || $audience === 'member') {
                $audience = 'member';
            } elseif ($audience === 'loggedout' || $audience === 'guest') {
                $audience = 'guest';
            }
            if (!in_array($audience, ['all', 'guest', 'member'], true)) {
                $audience = 'all';
            }
            if ($label === '' || $url === '') {
                continue;
            }
            $normalized[] = $label . '|' . $url . '|' . $audience . '|' . $group;
            if (count($normalized) >= 16) {
                break;
            }
        }

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

    public function sanitize_nav_menu_id_option($value) {
        $menu_id = absint((int) $value);
        if ($menu_id <= 0) {
            return 0;
        }
        $menu_object = wp_get_nav_menu_object($menu_id);
        if (!$menu_object instanceof WP_Term) {
            return 0;
        }
        return (int) $menu_object->term_id;
    }

    public function sanitize_sticky_nav_role_menu_map_option($value) {
        if (!is_array($value)) {
            return [];
        }

        $editable_roles = function_exists('get_editable_roles') ? (array) get_editable_roles() : [];
        $sanitized = [];

        foreach ($value as $role_key => $menu_id) {
            $role_key = sanitize_key((string) $role_key);
            if ($role_key === '' || !isset($editable_roles[$role_key])) {
                continue;
            }
            $normalized_menu_id = $this->sanitize_nav_menu_id_option($menu_id);
            if ($normalized_menu_id > 0) {
                $sanitized[$role_key] = $normalized_menu_id;
            }
        }

        return $sanitized;
    }

    public function sanitize_footer_menu_count_option($value) {
        $count = absint((int) $value);
        if ($count < 1) {
            $count = 1;
        } elseif ($count > 5) {
            $count = 5;
        }
        return $count;
    }

    public function sanitize_footer_wp_menu_list_option($value) {
        if (!is_array($value)) {
            return [];
        }

        $sanitized = [];
        foreach ($value as $menu_id) {
            $normalized_menu_id = $this->sanitize_nav_menu_id_option($menu_id);
            if ($normalized_menu_id > 0) {
                $sanitized[$normalized_menu_id] = $normalized_menu_id;
            }
            if (count($sanitized) >= 5) {
                break;
            }
        }

        return array_values($sanitized);
    }

    public function sanitize_footer_bottom_text_option($value) {
        $value = sanitize_text_field((string) $value);
        $value = trim((string) preg_replace('/\s+/u', ' ', $value));
        return function_exists('mb_substr') ? (string) mb_substr($value, 0, 190) : substr($value, 0, 190);
    }


    public function sanitize_extension_app_pass_option($value) {
        $value = trim((string) $value);
        $value = preg_replace('/[^A-Za-z0-9_\-]/', '', $value);
        $value = substr((string) $value, 0, 128);
        if ($value === '') {
            $existing = trim((string) get_option('nexdirectory_extension_app_pass', ''));
            if ($existing !== '') {
                if ((string) get_option('nexdirectory_extension_app_pass_hash', '') === '') {
                    update_option('nexdirectory_extension_app_pass_hash', wp_hash_password($existing), false);
                }
                delete_option('nexdirectory_extension_app_pass');
            }
            return '';
        }
        update_option('nexdirectory_extension_app_pass_hash', wp_hash_password($value), false);
        return '';
    }


    private function get_extension_app_pass_transient_key() {
        return 'nexdirectory_extension_app_pass_once_' . (string) get_current_user_id();
    }


    private function get_extension_app_pass($create_if_missing = true) {
        $one_time = (string) get_transient($this->get_extension_app_pass_transient_key());
        if ($one_time !== '') {
            // Consume the redirect hand-off immediately. The secret remains in
            // the rendered page for copying, but cannot be recovered by
            // refreshing or revisiting the screen.
            delete_transient($this->get_extension_app_pass_transient_key());
            return $one_time;
        }

        $current = trim((string) get_option('nexdirectory_extension_app_pass', ''));
        if ($current !== '') {
            if ((string) get_option('nexdirectory_extension_app_pass_hash', '') === '') {
                update_option('nexdirectory_extension_app_pass_hash', wp_hash_password($current), false);
            }
            delete_option('nexdirectory_extension_app_pass');
            return $current;
        }
        if ((string) get_option('nexdirectory_extension_app_pass_hash', '') !== '') {
            return '';
        }
        if (!$create_if_missing) {
            return '';
        }
        $generated = wp_generate_password(48, false, false);
        update_option('nexdirectory_extension_app_pass_hash', wp_hash_password($generated), false);
        delete_option('nexdirectory_extension_app_pass');
        return $generated;
    }


    private function extension_app_pass_matches($provided_pass) {
        $provided_pass = trim((string) $provided_pass);
        if ($provided_pass === '') {
            return false;
        }
        $hash = trim((string) get_option('nexdirectory_extension_app_pass_hash', ''));
        if ($hash !== '' && wp_check_password($provided_pass, $hash)) {
            return true;
        }
        $legacy_plaintext = trim((string) get_option('nexdirectory_extension_app_pass', ''));
        if ($legacy_plaintext === '') {
            return false;
        }
        $matches = hash_equals($legacy_plaintext, $provided_pass);
        if ($matches && $hash === '') {
            update_option('nexdirectory_extension_app_pass_hash', wp_hash_password($legacy_plaintext), false);
        }
        if ($matches) {
            delete_option('nexdirectory_extension_app_pass');
        }
        return $matches;
    }


    public function sanitize_map_results_per_page_option($value) {
        $count = absint((int) $value);
        if ($count < 12) {
            $count = 12;
        }
        if ($count > 250) {
            $count = 250;
        }
        return $count;
    }


    public function sanitize_hero_layout_option($value) {
        $value = sanitize_key((string) $value);
        return in_array($value, ['cinematic', 'compact', 'split', 'minimal', 'immersive', 'spotlight'], true) ? $value : 'cinematic';
    }


    public function sanitize_event_page_layout_option($value) {
        $value = sanitize_key((string) $value);
        $allowed = ['modern', 'single-column', 'editorial', 'fifty-fifty', 'left-sidebar', 'zig-zag', 'full3', 'zen'];
        return in_array($value, $allowed, true) ? $value : 'modern';
    }


    public function sanitize_event_gallery_type_option($value) {
        $value = sanitize_key((string) $value);
        $allowed = ['default', 'carousel', 'wall'];
        return in_array($value, $allowed, true) ? $value : 'default';
    }


    public function sanitize_smtp_port_option($value) {
        $port = absint((int) $value);
        if ($port < 1) {
            $port = 587;
        }
        if ($port > 65535) {
            $port = 65535;
        }
        return $port;
    }


    public function sanitize_smtp_encryption_option($value) {
        $value = strtolower(trim((string) $value));
        return in_array($value, ['', 'none', 'tls', 'ssl'], true) ? $value : 'tls';
    }


    public function sanitize_upload_limit_kb_option($value) {
        $v = absint((int) $value);
        if ($v < 64) {
            $v = 64;
        }
        if ($v > 20480) {
            $v = 20480;
        }
        return $v;
    }


    public function sanitize_upload_files_count_option($value) {
        $v = absint((int) $value);
        if ($v < 1) {
            $v = 1;
        }
        if ($v > 100) {
            $v = 100;
        }
        return $v;
    }


    public function sanitize_custom_form_fields_option($value) {
        $decoded = [];
        if (is_string($value)) {
            $decoded = json_decode($value, true);
            if (!is_array($decoded)) {
                $decoded = [];
            }
        } elseif (is_array($value)) {
            $decoded = $value;
        }

        if (isset($decoded['id']) && is_array($decoded['id'])) {
            $rows = [];
            foreach ((array) $decoded['id'] as $index => $id_value) {
                $rows[] = [
                    'id' => $id_value,
                    'label' => isset($decoded['label'][$index]) ? $decoded['label'][$index] : '',
                    'type' => isset($decoded['type'][$index]) ? $decoded['type'][$index] : 'text',
                    'placeholder' => isset($decoded['placeholder'][$index]) ? $decoded['placeholder'][$index] : '',
                    'help' => isset($decoded['help'][$index]) ? $decoded['help'][$index] : '',
                    'required' => isset($decoded['required'][$index]) ? $decoded['required'][$index] : '0',
                    'show_in_event' => isset($decoded['show_in_event'][$index]) ? $decoded['show_in_event'][$index] : '1',
                    'options' => isset($decoded['options'][$index]) ? $decoded['options'][$index] : '',
                ];
            }
            $decoded = $rows;
        }

        $sanitized = [];
        $seen_ids = [];
        foreach ((array) $decoded as $index => $row) {
            if (!is_array($row)) {
                continue;
            }
            $field = $this->sanitize_custom_field_definition($row, (int) $index);
            if ($field['id'] === '' || isset($seen_ids[$field['id']])) {
                continue;
            }
            $seen_ids[$field['id']] = true;
            $sanitized[] = $field;
        }

        return wp_json_encode(array_values($sanitized));
    }


    public function sanitize_custom_taxonomies_option($value) {
        $rows = [];
        if (is_array($value)) {
            $rows = $value;
        } else {
            $raw = (string) $value;
            $lines = preg_split('/\r\n|\r|\n/', $raw);
            if (!is_array($lines)) {
                $lines = [];
            }
            foreach ($lines as $line) {
                $line = trim((string) $line);
                if ($line === '' || strpos($line, '#') === 0) {
                    continue;
                }
                $parts = array_map('trim', explode('|', $line));
                $rows[] = [
                    'slug' => $parts[0] ?? '',
                    'label' => $parts[1] ?? '',
                    'hierarchical' => $parts[2] ?? '1',
                    'show_on_submit' => $parts[3] ?? '1',
                    'show_on_overview' => $parts[4] ?? '1',
                ];
            }
        }

        $reserved = [
            self::TAX_TYPE,
            self::TAX_MODE,
            self::TAX_TAG,
            self::TAX_PRICE,
            'category',
            'post_tag',
        ];
        $normalized = [];
        $seen = [];
        foreach ((array) $rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $slug = nexdirectory_sanitize_title((string) ($row['slug'] ?? ''));
            if ($slug === '') {
                continue;
            }
            if (strpos($slug, 'nexdirectory_') !== 0) {
                $slug = 'nexdirectory_' . $slug;
            }
            $slug = substr($slug, 0, 32);
            if (in_array($slug, $reserved, true) || isset($seen[$slug])) {
                continue;
            }
            $label = sanitize_text_field((string) ($row['label'] ?? ''));
            if ($label === '') {
                $label = ucwords(str_replace(['_', '-'], ' ', preg_replace('/^nexdirectory_/', '', $slug)));
            }
            $to_bool_flag = static function ($input, $default = '1') {
                $value = strtolower(trim((string) $input));
                if (in_array($value, ['1', 'true', 'yes', 'y', 'on'], true)) {
                    return '1';
                }
                if (in_array($value, ['0', 'false', 'no', 'n', 'off'], true)) {
                    return '0';
                }
                return $default;
            };
            $normalized[] = [
                'slug' => $slug,
                'label' => $label,
                'hierarchical' => $to_bool_flag($row['hierarchical'] ?? '1', '1'),
                'show_on_submit' => $to_bool_flag($row['show_on_submit'] ?? '1', '1'),
                'show_on_overview' => $to_bool_flag($row['show_on_overview'] ?? '1', '1'),
            ];
            $seen[$slug] = true;
        }

        return wp_json_encode(array_values($normalized));
    }


    private function get_pending_events_count() {
        $counts = wp_count_posts(self::POST_TYPE);
        if (!$counts || !isset($counts->pending)) {
            return 0;
        }
        return max(0, (int) $counts->pending);
    }


    private function get_pending_claim_requests_count() {
        $count = get_comments([
            'type' => 'nexdirectory_claim',
            'status' => 'hold',
            'count' => true,
            'meta_query' => [
                'relation' => 'OR',
                [
                    'key' => 'nexdirectory_claim_status',
                    'value' => 'pending',
                    'compare' => '=',
                ],
                [
                    'key' => 'nexdirectory_claim_status',
                    'compare' => 'NOT EXISTS',
                ],
            ],
        ]);
        if (!is_numeric($count)) {
            return 0;
        }
        return max(0, (int) $count);
    }


    private function get_user_reports_count() {
        $count = get_comments([
            'type' => 'nexdirectory_report',
            'status' => 'approve',
            'count' => true,
        ]);
        if (!is_numeric($count)) {
            return 0;
        }
        return max(0, (int) $count);
    }


    private function get_open_moderation_items_count() {
        return $this->get_pending_events_count()
            + $this->get_pending_claim_requests_count()
            + $this->get_user_reports_count();
    }


    private function sync_role_capabilities_from_settings() {
        $role_flags = [
            'event_submitter' => [
                'nexdirectory_submit_events' => (string) get_option('nexdirectory_role_event_submitter_can_submit', '1') === '1',
            ],
            'author' => [
                'nexdirectory_submit_events' => (string) get_option('nexdirectory_role_author_can_submit', '1') === '1',
            ],
            'editor' => [
                'nexdirectory_moderate_events' => (string) get_option('nexdirectory_role_editor_can_moderate', '1') === '1',
                'nexdirectory_manage_organizers' => (string) get_option('nexdirectory_role_editor_manage_organizers', '1') === '1',
            ],
        ];
        foreach ($role_flags as $role_name => $capabilities) {
            $role = get_role($role_name);
            if (!$role instanceof WP_Role) {
                continue;
            }
            foreach ($capabilities as $capability => $enabled) {
                if ($enabled) {
                    $role->add_cap($capability, true);
                } else {
                    $role->remove_cap($capability);
                }
            }
        }
    }

    /**
     * Applies the small amount of runtime work required after relevant options
     * change instead of repeating it on every request.
     *
     * @param string $option Option name.
     * @param mixed  $old_value Previous value, or the added value for added_option.
     * @param mixed  $new_value New value.
     */
    public function handle_runtime_option_change($option, $old_value = null, $new_value = null) {
        $option = sanitize_key((string) $option);
        $role_options = [
            'nexdirectory_role_event_submitter_can_submit',
            'nexdirectory_role_author_can_submit',
            'nexdirectory_role_editor_can_moderate',
            'nexdirectory_role_editor_manage_organizers',
        ];
        if (in_array($option, $role_options, true)) {
            $this->sync_role_capabilities_from_settings();
            return;
        }

        if ($option === 'nexdirectory_use_action_scheduler') {
            $this->schedule_privacy_retention_cleanup();
            do_action('nexdirectory_extension_schedules_refresh', $option);
            return;
        }
    }

    /**
     * Returns normalized demo-mode usage details for admin screens.
     *
     * @return array<string, mixed>
     */
    private function get_demo_limit_admin_context() {
        return [
            'is_demo_mode' => false,
            'pro_addon_active' => false,
            'pro_license_active' => false,
            'unlimited' => true,
            'limit' => 0,
            'used' => 0,
            'remaining' => null,
            'is_at_limit' => false,
            'upgrade_url' => '',
            'license_url' => admin_url('plugins.php'),
            'license_label' => __('Manage Add-ons', 'nexdirectory'),
        ];
    }


    public function render_dashboard_page() {
        if (!current_user_can('manage_options')) {
            return;
        }

        global $wpdb;

        $brand_name = $this->get_admin_brand_name();
        $user = wp_get_current_user();
        $display_name = '';
        if ($user instanceof WP_User) {
            $display_name = trim((string) ($user->display_name ?: $user->user_login));
        }
        if ($display_name === '') {
            $display_name = __('Admin', 'nexdirectory');
        }

        $profile_key = method_exists($this, 'get_site_profile_key')
            ? (string) $this->get_site_profile_key()
            : 'events';
        $profile_config = method_exists($this, 'get_site_profile_config')
            ? (array) $this->get_site_profile_config($profile_key)
            : [];
        $mode_label = sanitize_text_field((string) ($profile_config['label'] ?? ucfirst(str_replace('_', ' ', $profile_key))));
        if ($mode_label === '') {
            $mode_label = __('Events (default)', 'nexdirectory');
        }

        $counts = wp_count_posts(self::POST_TYPE);
        $published_count = (int) ($counts->publish ?? 0);
        $pending_count = (int) ($counts->pending ?? 0);
        $draft_count = (int) ($counts->draft ?? 0);
        $expired_count = (int) ($counts->expired ?? 0);
        $private_count = (int) ($counts->private ?? 0);
        $future_count = (int) ($counts->future ?? 0);
        $total_listings = $published_count + $pending_count + $draft_count + $private_count + $future_count;

        $users_summary = function_exists('count_users') ? (array) count_users() : [];
        $total_users = (int) ($users_summary['total_users'] ?? 0);
        $active_users = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(DISTINCT post_author) FROM {$wpdb->posts} WHERE post_type = %s AND post_status IN ('publish','pending','draft','future')",
                self::POST_TYPE
            )
        );

        $rsvp_total = 0;
        $rsvp_table = $wpdb->prefix . self::RSVP_TABLE;
        $rsvp_table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $rsvp_table));
        if (is_string($rsvp_table_exists) && $rsvp_table_exists !== '') {
            $rsvp_total = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$rsvp_table}");
        }

        $submit_url = $this->get_platform_page_url(
            'nexdirectory_page_submit_event_url',
            $this->get_profile_route_slugs('submit', $profile_key),
            $this->get_profile_route_default_url('submit', $profile_key)
        );

        $map_provider = sanitize_key((string) get_option('nexdirectory_map_provider', 'openstreetmap'));
        $map_ready = true;
        if ($map_provider === 'google') {
            $map_ready = trim((string) get_option('nexdirectory_google_maps_api_key', '')) !== '';
        } elseif ($map_provider === 'mapbox') {
            $map_ready = trim((string) get_option('nexdirectory_mapbox_access_token', '')) !== '';
        }
        $smtp_ready = (string) get_option('nexdirectory_smtp_enabled', '0') === '1' && trim((string) get_option('nexdirectory_smtp_host', '')) !== '';
        $pro_addon_active = function_exists('nexdirectory_is_pro_addon_active') && nexdirectory_is_pro_addon_active();
        $pro_license_active = function_exists('nexdirectory_is_pro_license_active') && nexdirectory_is_pro_license_active();
        $license_label = $pro_license_active
            ? __('Pro active', 'nexdirectory')
            : ($pro_addon_active ? __('Pro needs licence', 'nexdirectory') : __('Free', 'nexdirectory'));
        $license_class = 'nexdirectory-status-ok';
        if ($pro_addon_active && !$pro_license_active) {
            $license_class = 'nexdirectory-status-warn';
        }
        $allowance_label = __('Unlimited', 'nexdirectory');
        $allowance_summary = __('There is no built-in publishing limit for listings or events.', 'nexdirectory');
        $license_status_summary = $pro_license_active
            ? __('Nex Directory Pro is connected. Extra modules are available, and publishing remains unlimited.', 'nexdirectory')
            : __('You can keep publishing listings without Pro. Pro is an optional add-on for advanced workflows.', 'nexdirectory');
        $pro_addons_url = $pro_addon_active
            ? admin_url('admin.php?page=nexdirectory-pro')
            : $this->get_free_pro_comparison_url();
        $pro_addons_label = $pro_addon_active
            ? __('Manage Pro', 'nexdirectory')
            : __('Compare Free & Pro', 'nexdirectory');

        // phpcs:disable WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Read-only redirect feedback is normalized immediately below.
        $redirect_notice_raw = isset($_GET['nexdirectory_notice'])
            ? (string) wp_unslash($_GET['nexdirectory_notice'])
            : '';
        $redirect_notice_type = isset($_GET['nexdirectory_notice_type'])
            ? sanitize_key((string) wp_unslash($_GET['nexdirectory_notice_type']))
            : 'success';
        // phpcs:enable WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
        $redirect_notice = sanitize_text_field(rawurldecode($redirect_notice_raw));
        if ($redirect_notice_type === 'warn') {
            $redirect_notice_type = 'warning';
        }
        if (!in_array($redirect_notice_type, ['success', 'error', 'warning', 'info'], true)) {
            $redirect_notice_type = 'success';
        }

        ?>
        <div class="wrap nexdirectory-admin-premium nexdirectory-dashboard-page">
            <?php $this->render_admin_premium_shell_styles(); ?>
            <?php $this->enqueue_local_style_asset('includes-trait-nexdirectory-admin-php-style-1', 'assets/generated/includes-trait-nexdirectory-admin-php-style-1.css'); ?>

            <?php if ($redirect_notice !== '') : ?>
                <div class="notice notice-<?php echo esc_attr($redirect_notice_type); ?> is-dismissible nexdirectory-admin-redirect-notice" role="status" aria-live="polite">
                    <p><strong><?php echo esc_html($redirect_notice); ?></strong></p>
                </div>
            <?php endif; ?>

            <section class="nexdirectory-admin-hero">
                <span class="nexdirectory-admin-hero-kicker"><?php esc_html_e('Welcome Dashboard', 'nexdirectory'); ?></span>
                <?php /* translators: %s: Placeholder value. */ ?>
                <h1><?php echo esc_html(sprintf(__('Welcome back, %s', 'nexdirectory'), $display_name)); ?> <span aria-hidden="true">👋</span></h1>
                <p>
                    <?php
                    echo esc_html(
                        sprintf(
                            /* translators: %1$s: Placeholder value. */
                            __('This is your %1$s control center. Manage listings, moderation, imports, and community activity from one place.', 'nexdirectory'),
                            $brand_name
                        )
                    );
                    ?>
                </p>
                <div class="nexdirectory-dashboard-status-line">
                    <span class="nexdirectory-dashboard-chip"><strong><?php esc_html_e('Current Mode:', 'nexdirectory'); ?></strong><?php echo esc_html($mode_label); ?></span>
                    <span class="nexdirectory-dashboard-chip"><strong><?php esc_html_e('Total Listings:', 'nexdirectory'); ?></strong><?php echo esc_html((string) $total_listings); ?></span>
                    <span class="nexdirectory-dashboard-chip"><strong><?php esc_html_e('Pending Moderation:', 'nexdirectory'); ?></strong><?php echo esc_html((string) $pending_count); ?></span>
                    <span class="nexdirectory-dashboard-chip"><strong><?php esc_html_e('Active Users:', 'nexdirectory'); ?></strong><?php echo esc_html((string) $active_users); ?></span>
                    <span class="nexdirectory-dashboard-chip <?php echo esc_attr($license_class); ?>"><strong><?php esc_html_e('Plan:', 'nexdirectory'); ?></strong><?php echo esc_html($license_label); ?></span>
                    <span class="nexdirectory-dashboard-chip nexdirectory-status-ok" title="<?php echo esc_attr($allowance_summary); ?>"><strong><?php esc_html_e('Publishing:', 'nexdirectory'); ?></strong><?php echo esc_html($allowance_label); ?></span>
                </div>
            </section>

            <section class="nexdirectory-admin-panel nexdirectory-license-card <?php echo $pro_addon_active ? 'has-pro-addon' : 'is-free-plan'; ?>" aria-label="<?php esc_attr_e('Current plan and optional Pro add-on', 'nexdirectory'); ?>">
                <div class="nexdirectory-license-card-copy">
                    <span class="nexdirectory-license-card-kicker"><?php esc_html_e('Current plan', 'nexdirectory'); ?></span>
                    <h3><?php echo esc_html($pro_license_active ? __('Nex Directory Pro', 'nexdirectory') : __('Nex Directory - unlimited', 'nexdirectory')); ?></h3>
                    <p><?php echo esc_html($license_status_summary); ?></p>
                    <?php if (!$pro_addon_active) : ?>
                        <ul class="nexdirectory-dashboard-pro-benefits">
                            <li><span class="dashicons dashicons-clock" aria-hidden="true"></span><?php esc_html_e('Automate reminders and review summaries', 'nexdirectory'); ?></li>
                            <li><span class="dashicons dashicons-tickets-alt" aria-hidden="true"></span><?php esc_html_e('Add payments, ticketing, boosts, and QR check-in', 'nexdirectory'); ?></li>
                            <li><span class="dashicons dashicons-smartphone" aria-hidden="true"></span><?php esc_html_e('Offer AI discovery and an Event Web App', 'nexdirectory'); ?></li>
                        </ul>
                    <?php endif; ?>
                </div>
                <div class="nexdirectory-license-card-actions">
                    <a class="button <?php echo !$pro_addon_active ? 'button-primary' : ''; ?>" href="<?php echo esc_url($pro_addons_url); ?>"><?php echo esc_html($pro_addons_label); ?></a>
                    <?php if (!$pro_addon_active) : ?>
                        <a class="button" href="<?php echo esc_url(nexdirectory_get_pro_upgrade_url()); ?>" target="_blank" rel="noopener noreferrer external"><?php esc_html_e('View Pro plans', 'nexdirectory'); ?></a>
                    <?php endif; ?>
                </div>
            </section>

            <section class="nexdirectory-admin-panel">
                <h2><span class="dashicons dashicons-chart-area" aria-hidden="true"></span><?php esc_html_e('At-a-Glance Stats', 'nexdirectory'); ?></h2>
                <div class="nexdirectory-admin-grid">
                    <div class="nexdirectory-admin-stat-card"><p class="nexdirectory-admin-stat-label"><?php esc_html_e('Total Listings', 'nexdirectory'); ?></p><p class="nexdirectory-admin-stat-value"><?php echo esc_html((string) $total_listings); ?></p></div>
                    <div class="nexdirectory-admin-stat-card"><p class="nexdirectory-admin-stat-label"><?php esc_html_e('Published', 'nexdirectory'); ?></p><p class="nexdirectory-admin-stat-value"><?php echo esc_html((string) $published_count); ?></p></div>
                    <div class="nexdirectory-admin-stat-card"><p class="nexdirectory-admin-stat-label"><?php esc_html_e('Pending', 'nexdirectory'); ?></p><p class="nexdirectory-admin-stat-value"><?php echo esc_html((string) $pending_count); ?></p></div>
                    <div class="nexdirectory-admin-stat-card"><p class="nexdirectory-admin-stat-label"><?php esc_html_e('Expired', 'nexdirectory'); ?></p><p class="nexdirectory-admin-stat-value"><?php echo esc_html((string) $expired_count); ?></p></div>
                    <div class="nexdirectory-admin-stat-card"><p class="nexdirectory-admin-stat-label"><?php esc_html_e('RSVP Count', 'nexdirectory'); ?></p><p class="nexdirectory-admin-stat-value"><?php echo esc_html((string) $rsvp_total); ?></p></div>
                    <div class="nexdirectory-admin-stat-card"><p class="nexdirectory-admin-stat-label"><?php esc_html_e('Total Users', 'nexdirectory'); ?></p><p class="nexdirectory-admin-stat-value"><?php echo esc_html((string) $total_users); ?></p></div>
                </div>
            </section>

            <section class="nexdirectory-admin-panel">
                <h2><span class="dashicons dashicons-superhero" aria-hidden="true"></span><?php esc_html_e('Quick Actions', 'nexdirectory'); ?></h2>
                <div class="nexdirectory-admin-quick-actions">
                    <a class="button button-primary" href="<?php echo esc_url($submit_url); ?>"><span class="dashicons dashicons-plus-alt2" aria-hidden="true"></span><?php esc_html_e('Add New Listing', 'nexdirectory'); ?></a>
                    <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=nexdirectory-import')); ?>"><span class="dashicons dashicons-upload" aria-hidden="true"></span><?php esc_html_e('Import Listings', 'nexdirectory'); ?></a>
                    <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=nexdirectory-settings#nexdirectory-tab-pages')); ?>"><span class="dashicons dashicons-layout" aria-hidden="true"></span><?php esc_html_e('Configure Templates', 'nexdirectory'); ?></a>
                    <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=nexdirectory-settings')); ?>"><span class="dashicons dashicons-admin-generic" aria-hidden="true"></span><?php esc_html_e('Settings', 'nexdirectory'); ?></a>
                    <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=nexdirectory-diagnostics')); ?>"><span class="dashicons dashicons-shield-alt" aria-hidden="true"></span><?php esc_html_e('Run Diagnostics', 'nexdirectory'); ?></a>
                    <?php if (function_exists('nexdirectory_is_pro_feature_enabled') && nexdirectory_is_pro_feature_enabled('advanced_analytics')) : ?>
                        <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=nexdirectory-metrics')); ?>"><span class="dashicons dashicons-chart-bar" aria-hidden="true"></span><?php esc_html_e('Advanced Analytics', 'nexdirectory'); ?></a>
                    <?php else : ?>
                        <a class="button" href="<?php echo esc_url($this->get_free_pro_comparison_url('analytics')); ?>"><span class="dashicons dashicons-chart-bar" aria-hidden="true"></span><?php esc_html_e('Compare Analytics', 'nexdirectory'); ?></a>
                    <?php endif; ?>
                </div>
            </section>

            <section class="nexdirectory-admin-panel nexdirectory-admin-two-col">
                <div class="nexdirectory-dashboard-help-card">
                    <h2><span class="dashicons dashicons-video-alt3" aria-hidden="true"></span><?php esc_html_e('Tutorials & Help', 'nexdirectory'); ?></h2>
                    <p><?php esc_html_e('Open the documentation and support links in a new tab when you need setup guidance.', 'nexdirectory'); ?></p>
                    <p style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap;">
                        <a class="button button-primary" href="<?php echo esc_url('https://nexdirectory.com/tutorials'); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e('View All Tutorials', 'nexdirectory'); ?></a>
                        <a class="button" href="<?php echo esc_url('https://nexdirectory.com/support'); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Get Support', 'nexdirectory'); ?></a>
                    </p>
                </div>
                <div class="nexdirectory-dashboard-help-card">
                    <h2><span class="dashicons dashicons-yes-alt" aria-hidden="true"></span><?php esc_html_e('Getting Started Checklist', 'nexdirectory'); ?></h2>
                    <ul class="nexdirectory-dashboard-checklist">
                        <li><span class="nexdirectory-dashboard-check is-done">✓</span><?php esc_html_e('Choose template mode', 'nexdirectory'); ?></li>
                        <li><span class="nexdirectory-dashboard-check <?php echo $smtp_ready ? 'is-done' : ''; ?>"><?php echo $smtp_ready ? '✓' : '•'; ?></span><?php esc_html_e('Configure email delivery', 'nexdirectory'); ?></li>
                        <li><span class="nexdirectory-dashboard-check <?php echo $total_listings > 0 ? 'is-done' : ''; ?>"><?php echo $total_listings > 0 ? '✓' : '•'; ?></span><?php esc_html_e('Add first listing', 'nexdirectory'); ?></li>
                        <li><span class="nexdirectory-dashboard-check <?php echo $map_ready ? 'is-done' : ''; ?>"><?php echo $map_ready ? '✓' : '•'; ?></span><?php esc_html_e('Configure map provider', 'nexdirectory'); ?></li>
                        <li><span class="nexdirectory-dashboard-check <?php echo nexdirectory_is_pro_addon_active() ? 'is-done' : ''; ?>"><?php echo nexdirectory_is_pro_addon_active() ? '✓' : '•'; ?></span><?php esc_html_e('Connect optional Pro tools', 'nexdirectory'); ?></li>
                    </ul>
                </div>
            </section>

            <section class="nexdirectory-admin-panel">
                <h2><span class="dashicons dashicons-megaphone" aria-hidden="true"></span><?php esc_html_e('Updates & Changelog', 'nexdirectory'); ?></h2>
                <?php /* translators: %s: Placeholder value. */ ?>
                <p><strong><?php echo esc_html(sprintf(__('Current version: %s', 'nexdirectory'), (string) self::VERSION)); ?></strong></p>
                <ul class="nexdirectory-dashboard-update-list">
                    <li><?php esc_html_e('New profile-aware routing and template mode handling.', 'nexdirectory'); ?></li>
                    <li><?php esc_html_e('Improved import tooling and moderation workflows.', 'nexdirectory'); ?></li>
                </ul>
                <p style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap;">
                    <a class="button" href="<?php echo esc_url('https://nexdirectory.com/tutorials'); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Release Notes', 'nexdirectory'); ?></a>
                    <a class="button" href="<?php echo esc_url('https://nexdirectory.com/support'); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Roadmap & Support', 'nexdirectory'); ?></a>
                </p>
            </section>
        </div>
        <?php
    }


    public function render_settings_page() {
        if (!current_user_can('manage_options')) {
            return;
        }

        $settings_markup = NEXDIRECTORY_Escaping::prepare_markup_for_wp_kses((string) $this->render_admin_template('settings/page.php'));
        echo wp_kses($settings_markup, NEXDIRECTORY_Escaping::allowed_html($settings_markup));
    }


    public function render_admin_moderation_page() {
        if (!current_user_can('manage_options')) {
            return;
        }
        $settings_brand_name = $this->get_admin_brand_name();
        $ui_labels = method_exists($this, 'get_site_profile_ui_labels') ? (array) $this->get_site_profile_ui_labels() : [];
        $plural_label = sanitize_text_field((string) ($ui_labels['plural'] ?? __('Events', 'nexdirectory')));
        ?>
        <div class="wrap nexdirectory-admin-premium nexdirectory-settings-premium nexdirectory-moderation-admin-page">
            <?php $this->render_admin_premium_shell_styles(); ?>
            <?php $this->enqueue_local_style_asset('includes-trait-nexdirectory-admin-php-style-2', 'assets/generated/includes-trait-nexdirectory-admin-php-style-2.css'); ?>
            <section class="nexdirectory-admin-hero">
                <div>
                    <span class="nexdirectory-admin-hero-kicker"><?php esc_html_e('Directory Operations', 'nexdirectory'); ?></span>
                    <h1><?php esc_html_e('Moderation Queue', 'nexdirectory'); ?></h1>
                    <?php /* translators: %s: Placeholder value. */ ?>
                    <p><?php echo esc_html(sprintf(__('Review pending %s, claim requests, and reports with fast approve/reject workflows.', 'nexdirectory'), strtolower((string) $plural_label))); ?></p>
                    <div class="nexdirectory-admin-hero-badges">
                        <span class="nexdirectory-admin-hero-badge"><span class="dashicons dashicons-yes-alt" aria-hidden="true"></span><?php esc_html_e('Bulk Actions', 'nexdirectory'); ?></span>
                        <span class="nexdirectory-admin-hero-badge"><span class="dashicons dashicons-flag" aria-hidden="true"></span><?php esc_html_e('Reports & Claims', 'nexdirectory'); ?></span>
                    </div>
                </div>
                <div class="nexdirectory-admin-hero-action">
                    <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=nexdirectory-settings')); ?>">
                        <span class="dashicons dashicons-admin-settings" aria-hidden="true"></span>
                        <?php /* translators: %s: Placeholder value. */ ?>
                        <?php echo esc_html(sprintf(__('Back to %s Settings', 'nexdirectory'), $settings_brand_name)); ?>
                    </a>
                </div>
            </section>
            <?php echo wp_kses_post((string) $this->render_notice()); ?>
            <?php
            $moderation_markup = NEXDIRECTORY_Escaping::prepare_markup_for_wp_kses((string) $this->render_moderation_queue(['pending_limit' => 30, 'reports_limit' => 30, 'admin_context' => 1]));
            echo wp_kses($moderation_markup, NEXDIRECTORY_Escaping::allowed_html($moderation_markup));
            ?>
        </div>
        <?php
    }


    public function render_admin_user_management_page() {
        if (!current_user_can('manage_options')) {
            return;
        }
        $settings_brand_name = $this->get_admin_brand_name();
        $ui_labels = method_exists($this, 'get_site_profile_ui_labels') ? (array) $this->get_site_profile_ui_labels() : [];
        $owner_plural = sanitize_text_field((string) ($ui_labels['owner_plural'] ?? __('Organizers', 'nexdirectory')));
        $plural_label = sanitize_text_field((string) ($ui_labels['plural'] ?? __('Events', 'nexdirectory')));
        ?>
        <div class="wrap nexdirectory-admin-premium nexdirectory-settings-premium nexdirectory-user-management-admin-page">
            <?php $this->render_admin_premium_shell_styles(); ?>
            <?php $this->enqueue_local_style_asset('includes-trait-nexdirectory-admin-php-style-3', 'assets/generated/includes-trait-nexdirectory-admin-php-style-3.css'); ?>
            <section class="nexdirectory-admin-hero">
                <div>
                    <span class="nexdirectory-admin-hero-kicker"><?php esc_html_e('Directory Operations', 'nexdirectory'); ?></span>
                    <h1><?php esc_html_e('User Management', 'nexdirectory'); ?></h1>
                    <?php /* translators: 1: Placeholder value 1, 2: Placeholder value 2. */ ?>
                    <p><?php echo esc_html(sprintf(__('Manage %1$s roles, trust level, and %2$s ownership actions from one control panel.', 'nexdirectory'), strtolower((string) $owner_plural), strtolower((string) $plural_label))); ?></p>
                    <div class="nexdirectory-admin-hero-badges">
                        <span class="nexdirectory-admin-hero-badge"><span class="dashicons dashicons-admin-users" aria-hidden="true"></span><?php esc_html_e('Role Controls', 'nexdirectory'); ?></span>
                        <span class="nexdirectory-admin-hero-badge"><span class="dashicons dashicons-yes-alt" aria-hidden="true"></span><?php esc_html_e('Verification Tools', 'nexdirectory'); ?></span>
                    </div>
                </div>
                <div class="nexdirectory-admin-hero-action">
                    <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=nexdirectory-settings')); ?>">
                        <span class="dashicons dashicons-admin-settings" aria-hidden="true"></span>
                        <?php /* translators: %s: Placeholder value. */ ?>
                        <?php echo esc_html(sprintf(__('Back to %s Settings', 'nexdirectory'), $settings_brand_name)); ?>
                    </a>
                </div>
            </section>
            <?php echo wp_kses_post((string) $this->render_notice()); ?>
            <?php
            $user_management_markup = NEXDIRECTORY_Escaping::prepare_markup_for_wp_kses((string) $this->render_user_management(['limit' => 100, 'admin_context' => 1]));
            echo wp_kses($user_management_markup, NEXDIRECTORY_Escaping::allowed_html($user_management_markup));
            ?>
        </div>
        <?php
    }


                            public function handle_send_test_email() {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Not allowed.', 'nexdirectory'));
        }
        if (
            !isset($_POST['nexdirectory_send_test_email_nonce'])
            || !wp_verify_nonce(sanitize_text_field((string) wp_unslash($_POST['nexdirectory_send_test_email_nonce'])), 'nexdirectory_send_test_email')
        ) {
            wp_die(esc_html__('Invalid request.', 'nexdirectory'));
        }
        $to = sanitize_email((string) wp_unslash($_POST['nexdirectory_test_email_to'] ?? ''));
        if ($to === '') {
            $to = sanitize_email((string) get_option('admin_email'));
        }
        if (!is_email($to)) {
            $this->redirect_with_message(
                admin_url('admin.php?page=nexdirectory-settings#nexdirectory-tab-advanced'),
                __('Enter a valid recipient email for test delivery.', 'nexdirectory'),
                'error'
            );
        }
        $brand_name = $this->get_admin_brand_name();
        $subject = sprintf(
            /* translators: 1: Placeholder value 1, 2: Placeholder value 2. */
            __('%1$s SMTP Test (%2$s)', 'nexdirectory'),
            $brand_name,
            wp_date('Y-m-d H:i:s')
        );
        $body = sprintf(
            /* translators: 1: Placeholder value 1, 2: Placeholder value 2. */
            __('This is a test email from %1$s on %2$s. If you received this message, SMTP delivery is working.', 'nexdirectory'),
            $brand_name,
            home_url('/')
        );
        $sent = (bool) $this->send_platform_email($to, $subject, '', [
            'fallback_text' => $body,
            'email_heading' => __('SMTP Test Email', 'nexdirectory'),
        ]);
        if ($sent) {
            $this->append_runtime_log('info', 'SMTP test email sent', ['to' => $to]);
            $this->redirect_with_message(
                admin_url('admin.php?page=nexdirectory-settings#nexdirectory-tab-advanced'),
                /* translators: %s: Placeholder value. */
                sprintf(__('Test email sent to %s.', 'nexdirectory'), $to),
                'success'
            );
        }
        $this->append_runtime_log('error', 'SMTP test email failed', ['to' => $to]);
        $this->redirect_with_message(
            admin_url('admin.php?page=nexdirectory-settings#nexdirectory-tab-advanced'),
            __('Test email failed. Check SMTP settings, provider restrictions, and runtime logs.', 'nexdirectory'),
            'error'
        );
    }

    public function handle_test_map_connection() {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Not allowed.', 'nexdirectory'));
        }
        if (
            !isset($_POST['nexdirectory_test_map_connection_nonce'])
            || !wp_verify_nonce(sanitize_text_field((string) wp_unslash($_POST['nexdirectory_test_map_connection_nonce'])), 'nexdirectory_test_map_connection')
        ) {
            wp_die(esc_html__('Invalid request.', 'nexdirectory'));
        }

        $result = $this->run_settings_map_connection_test();
        if (!empty($result['ok'])) {
            $message = sanitize_text_field((string) ($result['message'] ?? __('Map connection test passed.', 'nexdirectory')));
            $this->append_runtime_log('info', 'Settings map connection test passed', [
                'message' => $message,
            ]);
            $this->redirect_with_message(
                admin_url('admin.php?page=nexdirectory-settings#nexdirectory-tab-design-maps--layouts'),
                $message,
                'success'
            );
        }

        $message = sanitize_text_field((string) ($result['message'] ?? __('Map connection test failed.', 'nexdirectory')));
        $this->append_runtime_log('error', 'Settings map connection test failed', [
            'message' => $message,
        ]);
        $this->redirect_with_message(
            admin_url('admin.php?page=nexdirectory-settings#nexdirectory-tab-design-maps--layouts'),
            $message,
            'error'
        );
    }

    public function handle_test_captcha_connection() {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Not allowed.', 'nexdirectory'));
        }
        if (
            !isset($_POST['nexdirectory_test_captcha_connection_nonce'])
            || !wp_verify_nonce(sanitize_text_field((string) wp_unslash($_POST['nexdirectory_test_captcha_connection_nonce'])), 'nexdirectory_test_captcha_connection')
        ) {
            wp_die(esc_html__('Invalid request.', 'nexdirectory'));
        }

        $result = $this->run_settings_captcha_connection_test();
        if (!empty($result['ok'])) {
            $message = sanitize_text_field((string) ($result['message'] ?? __('reCAPTCHA test passed.', 'nexdirectory')));
            $this->append_runtime_log('info', 'Settings captcha connection test passed', [
                'message' => $message,
            ]);
            $this->redirect_with_message(
                admin_url('admin.php?page=nexdirectory-settings#nexdirectory-tab-apis'),
                $message,
                'success'
            );
        }

        $message = sanitize_text_field((string) ($result['message'] ?? __('reCAPTCHA test failed.', 'nexdirectory')));
        $this->append_runtime_log('error', 'Settings captcha connection test failed', [
            'message' => $message,
        ]);
        $this->redirect_with_message(
            admin_url('admin.php?page=nexdirectory-settings#nexdirectory-tab-apis'),
            $message,
            'error'
        );
    }

    public function handle_test_routing_connection() {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Not allowed.', 'nexdirectory'));
        }
        if (
            !isset($_POST['nexdirectory_test_routing_connection_nonce'])
            || !wp_verify_nonce(sanitize_text_field((string) wp_unslash($_POST['nexdirectory_test_routing_connection_nonce'])), 'nexdirectory_test_routing_connection')
        ) {
            wp_die(esc_html__('Invalid request.', 'nexdirectory'));
        }

        $profile_key = method_exists($this, 'get_site_profile_key')
            ? sanitize_key((string) $this->get_site_profile_key())
            : 'events';
        $this->sync_route_options_to_existing_pages($profile_key);
        $health = $this->get_settings_routing_health($profile_key);
        $issues = [];
        if (!empty($health['issues']) && is_array($health['issues'])) {
            foreach ((array) $health['issues'] as $issue) {
                $issue = sanitize_text_field((string) $issue);
                if ($issue !== '') {
                    $issues[] = $issue;
                }
            }
        }

        if (!empty($health['connected'])) {
            $message = sanitize_text_field((string) ($health['summary'] ?? __('Routing test passed.', 'nexdirectory')));
            $this->append_runtime_log('info', 'Settings routing connection test passed', [
                'message' => $message,
            ]);
            $this->redirect_with_message(
                admin_url('admin.php?page=nexdirectory-settings#nexdirectory-tab-routing'),
                $message,
                'success'
            );
        }

        $base_message = sanitize_text_field((string) ($health['summary'] ?? __('Routing test failed.', 'nexdirectory')));
        if (!empty($issues)) {
            $base_message .= ' ' . sprintf(
                /* translators: %s: Placeholder value. */
                __('Issues: %s', 'nexdirectory'),
                implode(' | ', array_slice($issues, 0, 3))
            );
        }

        $this->append_runtime_log('error', 'Settings routing connection test failed', [
            'message' => $base_message,
        ]);
        $this->redirect_with_message(
            admin_url('admin.php?page=nexdirectory-settings#nexdirectory-tab-routing'),
            $base_message,
            'error'
        );
    }


    public function handle_regenerate_extension_app_pass() {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Not allowed.', 'nexdirectory'));
        }
        if (
            !isset($_POST['nexdirectory_regenerate_extension_app_pass_nonce'])
            || !wp_verify_nonce(sanitize_text_field((string) wp_unslash($_POST['nexdirectory_regenerate_extension_app_pass_nonce'])), 'nexdirectory_regenerate_extension_app_pass')
        ) {
            wp_die(esc_html__('Invalid request.', 'nexdirectory'));
        }

        $new_pass = wp_generate_password(48, false, false);
        update_option('nexdirectory_extension_app_pass_hash', wp_hash_password($new_pass), false);
        delete_option('nexdirectory_extension_app_pass');
        set_transient($this->get_extension_app_pass_transient_key(), $new_pass, 10 * MINUTE_IN_SECONDS);
        $this->append_runtime_log('info', 'Extension app pass regenerated', [
            'user_id' => (string) get_current_user_id(),
        ]);
        $redirect_url = admin_url('admin.php?page=nexdirectory-settings#nexdirectory-tab-advanced--extension');
        $requested_redirect = isset($_POST['nexdirectory_redirect_to'])
            ? esc_url_raw((string) wp_unslash($_POST['nexdirectory_redirect_to']))
            : '';
        if ($requested_redirect !== '') {
            $redirect_url = wp_validate_redirect($requested_redirect, $redirect_url);
        }
        $this->redirect_with_message(
            $redirect_url,
            __('Extension connection key created. Update the key in your Chrome extension.', 'nexdirectory'),
            'success'
        );
    }


    public function handle_generate_pages() {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Not allowed.', 'nexdirectory'));
        }
        if (
            !isset($_POST['nexdirectory_generate_pages_nonce'])
            || !wp_verify_nonce(sanitize_text_field((string) wp_unslash($_POST['nexdirectory_generate_pages_nonce'])), 'nexdirectory_generate_pages')
        ) {
            wp_die(esc_html__('Invalid request.', 'nexdirectory'));
        }
        $redirect_url = admin_url('admin.php?page=nexdirectory-settings#nexdirectory-tab-general');
        $requested_redirect = isset($_POST['nexdirectory_redirect_to']) ? esc_url_raw((string) wp_unslash($_POST['nexdirectory_redirect_to'])) : '';
        if ($requested_redirect !== '') {
            $redirect_url = wp_validate_redirect($requested_redirect, $redirect_url);
        }
        $this->ensure_default_pages();
        $profile_key = $this->get_site_profile_key();
        $this->sync_route_options_to_existing_pages($profile_key);
        $onboarding_step = isset($_POST['nexdirectory_onboarding_step'])
            ? absint((int) wp_unslash($_POST['nexdirectory_onboarding_step']))
            : 0;
        if ($onboarding_step >= 1 && $onboarding_step <= 6) {
            update_option('nexdirectory_onboarding_last_step', $onboarding_step, false);
        }
        $this->redirect_with_message(
            $redirect_url,
            __('Core page setup finished. Missing pages were created where their slugs were available, existing matching pages were preserved, and routes were refreshed.', 'nexdirectory'),
            'success'
        );
    }


    public function handle_mark_onboarding_complete() {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Not allowed.', 'nexdirectory'));
        }
        if (!isset($_POST['nexdirectory_onboarding_nonce']) || !wp_verify_nonce(sanitize_text_field((string) wp_unslash($_POST['nexdirectory_onboarding_nonce'])), 'nexdirectory_mark_onboarding_complete')) {
            wp_die(esc_html__('Invalid request.', 'nexdirectory'));
        }
        update_option('nexdirectory_onboarding_complete', '1', false);
        update_option('nexdirectory_onboarding_completed_at', current_time('mysql'), false);
        update_option('nexdirectory_onboarding_last_step', 6, false);
        delete_option('nexdirectory_activation_redirect_import');

        $fallback_redirect = admin_url('admin.php?page=nexdirectory-dashboard');
        $requested_redirect = isset($_POST['nexdirectory_redirect_to'])
            ? esc_url_raw((string) wp_unslash($_POST['nexdirectory_redirect_to']))
            : '';
        $redirect_url = $requested_redirect !== ''
            ? wp_validate_redirect($requested_redirect, $fallback_redirect)
            : $fallback_redirect;
        $this->redirect_with_message(
            $redirect_url,
            __('Setup complete. Your directory is ready for its first live listing.', 'nexdirectory'),
            'success'
        );
    }


    private function render_admin_premium_shell_styles() {
        ?>
        <?php $this->enqueue_local_style_asset('includes-trait-nexdirectory-admin-php-style-4', 'assets/generated/includes-trait-nexdirectory-admin-php-style-4.css'); ?>
        <?php
    }

    private function render_admin_template($template_file, array $context = []) {
        $template_slug = ltrim((string) $template_file, '/');
        if ($template_slug === '') {
            return '';
        }

        $template_path = plugin_dir_path(NEXDIRECTORY_PLUGIN_FILE) . 'templates/admin/' . $template_slug;
        if (!is_readable($template_path)) {
            return '';
        }

        ob_start();
        extract($context, EXTR_SKIP);
        include $template_path;
        return (string) ob_get_clean();
    }

    /**
     * Builds read-only readiness data for the setup wizard.
     *
     * @param string $active_site_profile Active starter profile key.
     * @return array<string,mixed>
     */
    private function get_onboarding_setup_context($active_site_profile = '') {
        $active_site_profile = sanitize_key((string) $active_site_profile);
        if ($active_site_profile === '') {
            $active_site_profile = 'events';
        }

        $core_page_expected = 18;
        $core_page_ids = get_posts([
            'post_type' => 'page',
            'post_status' => 'publish',
            'posts_per_page' => -1,
            'fields' => 'ids',
            'no_found_rows' => true,
            'meta_key' => '_nexdirectory_is_app_page',
            'meta_value' => '1',
        ]);
        $core_page_count = count((array) $core_page_ids);

        $listing_statuses = ['publish', 'pending', 'draft', 'private', 'future'];
        $listing_counts = wp_count_posts(self::POST_TYPE);
        $total_listing_count = 0;
        foreach ($listing_statuses as $listing_status) {
            $total_listing_count += isset($listing_counts->{$listing_status})
                ? absint((int) $listing_counts->{$listing_status})
                : 0;
        }
        $published_listing_count = isset($listing_counts->publish)
            ? absint((int) $listing_counts->publish)
            : 0;

        $demo_query = new WP_Query([
            'post_type' => self::POST_TYPE,
            'post_status' => $listing_statuses,
            'posts_per_page' => 1,
            'fields' => 'ids',
            'no_found_rows' => false,
            'meta_key' => 'nexdirectory_demo_imported',
            'meta_value' => '1',
        ]);
        $demo_listing_count = absint((int) $demo_query->found_posts);

        $show_on_front = sanitize_key((string) get_option('show_on_front', 'posts'));
        $homepage_id = $show_on_front === 'page' ? absint((int) get_option('page_on_front', 0)) : 0;
        $homepage_title = $homepage_id > 0 ? sanitize_text_field((string) get_the_title($homepage_id)) : '';
        $homepage_url = $homepage_id > 0 ? (string) get_permalink($homepage_id) : home_url('/');
        $whats_new_page = get_page_by_path('whats-new', OBJECT, 'page');
        $whats_new_page_id = $whats_new_page instanceof WP_Post ? absint((int) $whats_new_page->ID) : 0;

        $schema_ready = defined('NEXDIRECTORY_DB_VERSION')
            && (string) get_option('nexdirectory_db_version', '') === (string) NEXDIRECTORY_DB_VERSION
            && (string) get_option('nexdirectory_schema_verified_version', '') === (string) NEXDIRECTORY_DB_VERSION;
        $permalinks_ready = trim((string) get_option('permalink_structure', '')) !== '';
        $upload_data = function_exists('wp_get_upload_dir') ? wp_get_upload_dir() : wp_upload_dir(null, false, false);
        $upload_error = trim((string) ($upload_data['error'] ?? ''));
        $upload_base_dir = (string) ($upload_data['basedir'] ?? '');
        $uploads_ready = $upload_error === '' && $upload_base_dir !== '';
        $scheduled_tasks_ready = !(defined('DISABLE_WP_CRON') && DISABLE_WP_CRON);

        $preflight_checks = [
            [
                'key' => 'database',
                'label' => __('Directory data tables', 'nexdirectory'),
                'description' => $schema_ready
                    ? __('Database setup is current.', 'nexdirectory')
                    : __('The database setup needs a quick diagnostic check.', 'nexdirectory'),
                'ready' => $schema_ready,
                'action_url' => admin_url('admin.php?page=nexdirectory-diagnostics'),
                'action_label' => __('Open diagnostics', 'nexdirectory'),
            ],
            [
                'key' => 'permalinks',
                'label' => __('Visitor-friendly links', 'nexdirectory'),
                'description' => $permalinks_ready
                    ? __('Pretty permalinks are enabled.', 'nexdirectory')
                    : __('Plain permalinks work, but named links are recommended before launch.', 'nexdirectory'),
                'ready' => $permalinks_ready,
                'action_url' => admin_url('options-permalink.php'),
                'action_label' => __('Review permalinks', 'nexdirectory'),
            ],
            [
                'key' => 'uploads',
                'label' => __('Media uploads', 'nexdirectory'),
                'description' => $uploads_ready
                    ? __('WordPress can use the configured uploads location.', 'nexdirectory')
                    : __('Check the WordPress uploads location before adding local images.', 'nexdirectory'),
                'ready' => $uploads_ready,
                'action_url' => admin_url('site-health.php'),
                'action_label' => __('Open Site Health', 'nexdirectory'),
            ],
            [
                'key' => 'scheduled_tasks',
                'label' => __('Scheduled reminders', 'nexdirectory'),
                'description' => $scheduled_tasks_ready
                    ? __('WordPress scheduled tasks are available.', 'nexdirectory')
                    : __('WP-Cron is disabled; configure a server cron before relying on reminders.', 'nexdirectory'),
                'ready' => $scheduled_tasks_ready,
                'action_url' => admin_url('site-health.php'),
                'action_label' => __('Open Site Health', 'nexdirectory'),
            ],
        ];
        $preflight_ready_count = count(
            array_filter(
                $preflight_checks,
                static function ($check) {
                    return !empty($check['ready']);
                }
            )
        );

        $view_directory_url = method_exists($this, 'get_profile_route_default_url')
            ? (string) $this->get_profile_route_default_url('events', $active_site_profile)
            : home_url('/');

        return [
            'core_page_expected' => $core_page_expected,
            'core_page_count' => $core_page_count,
            'core_pages_ready' => $core_page_count >= $core_page_expected,
            'total_listing_count' => $total_listing_count,
            'published_listing_count' => $published_listing_count,
            'demo_listing_count' => $demo_listing_count,
            'homepage_id' => $homepage_id,
            'homepage_title' => $homepage_title,
            'homepage_url' => $homepage_url,
            'whats_new_page_id' => $whats_new_page_id,
            'is_whats_new_homepage' => $homepage_id > 0 && $homepage_id === $whats_new_page_id,
            'preflight_checks' => $preflight_checks,
            'preflight_ready_count' => $preflight_ready_count,
            'preflight_check_count' => count($preflight_checks),
            'add_listing_url' => admin_url('post-new.php?post_type=' . self::POST_TYPE),
            'manage_listings_url' => admin_url('edit.php?post_type=' . self::POST_TYPE),
            'view_directory_url' => $view_directory_url,
            'needs_attention_url' => admin_url('admin.php?page=nexdirectory-needs-attention'),
            'settings_url' => admin_url('admin.php?page=nexdirectory-settings'),
            'pages_url' => admin_url('edit.php?post_type=page'),
        ];
    }


    public function render_onboarding_page() {
        if (!current_user_can('manage_options')) {
            return;
        }
        $brand_name = $this->get_admin_brand_name();
        $done = (string) get_option('nexdirectory_onboarding_complete', '0') === '1';
        $ui_labels = method_exists($this, 'get_site_profile_ui_labels') ? (array) $this->get_site_profile_ui_labels() : [];
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Listing', 'nexdirectory')));
        $plural_label = sanitize_text_field((string) ($ui_labels['plural'] ?? __('Listings', 'nexdirectory')));
        $saved_step = absint((int) get_option('nexdirectory_onboarding_last_step', 1));
        if ($saved_step < 1 || $saved_step > 6) {
            $saved_step = 1;
        }
        $default_step = $done ? 6 : $saved_step;
        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Onboarding page reads query parameters only to choose the current step.
        $step_was_requested = isset($_GET['step']);
        $step = $step_was_requested ? absint((int) wp_unslash($_GET['step'])) : $default_step;
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
        if ($step < 1 || $step > 6) {
            $step = $default_step;
        }
        $wizard_steps = [
            1 => [
                'title' => __('Choose a Starter Blueprint', 'nexdirectory'),
                'short_title' => __('Blueprint', 'nexdirectory'),
                'icon' => 'fa-layer-group',
                'description' => __('Start with Events, a Local Directory, Jobs, or a Marketplace. Each blueprint configures the design, pages, routes, and sample content.', 'nexdirectory'),
                'help' => __('Blueprints are safe to reapply: they preserve live listings and only fill missing starter content.', 'nexdirectory'),
                'next_label' => __('Continue to Pages', 'nexdirectory'),
            ],
            2 => [
                'title' => __('Generate Core Pages', 'nexdirectory'),
                'short_title' => __('Pages', 'nexdirectory'),
                'icon' => 'fa-file-alt',
                /* translators: %1$s: Placeholder value. */
                'description' => sprintf(__('Create or refresh required pages for %1$s, submission, account, and routing.', 'nexdirectory'), $plural_label),
                'help' => __('Existing pages with the expected Nex Directory shortcode are kept. Pages using the same slug for other content are never overwritten.', 'nexdirectory'),
                'next_label' => __('Continue to Site Identity', 'nexdirectory'),
            ],
            3 => [
                'title' => __('Site Identity', 'nexdirectory'),
                'short_title' => __('Identity', 'nexdirectory'),
                'icon' => 'fa-paint-brush',
                'description' => __('Set your site name and brand accent so your UI is consistent from day one.', 'nexdirectory'),
                'help' => __('These values can be changed later in WordPress and Nex Directory settings.', 'nexdirectory'),
                'next_label' => __('Save & Continue', 'nexdirectory'),
            ],
            4 => [
                'title' => __('Import Demo Content', 'nexdirectory'),
                'short_title' => __('Samples', 'nexdirectory'),
                'icon' => 'fa-box-open',
                'description' => __('Load starter data so you can explore features quickly, then replace with your live content.', 'nexdirectory'),
                'help' => __('This step is optional. The import skips duplicates and does not delete or overwrite live listings.', 'nexdirectory'),
                'next_label' => __('Skip Sample Content', 'nexdirectory'),
                'optional' => true,
            ],
            5 => [
                'title' => __('Set Homepage', 'nexdirectory'),
                'short_title' => __('Homepage', 'nexdirectory'),
                'icon' => 'fa-house',
                'description' => __('Set a static homepage for your site. We recommend using What\'s New.', 'nexdirectory'),
                'help' => __('This step is optional. You can keep your current homepage and link to the directory from your site navigation.', 'nexdirectory'),
                'next_label' => __('Keep Current Homepage & Continue', 'nexdirectory'),
                'optional' => true,
            ],
            6 => [
                'title' => __('Launch Dashboard', 'nexdirectory'),
                'short_title' => __('Launch', 'nexdirectory'),
                'icon' => 'fa-rocket',
                'description' => __('Finish onboarding and jump straight into your admin dashboard.', 'nexdirectory'),
                'help' => __('Marking setup complete only dismisses the first-run prompt; every setting remains editable.', 'nexdirectory'),
                'next_label' => '',
            ],
        ];
        $operating_mode_was_saved = get_option('nexdirectory_onboarding_operating_mode', null) !== null;
        $operating_mode = sanitize_key((string) get_option('nexdirectory_onboarding_operating_mode', 'user'));
        if (!in_array($operating_mode, ['user', 'single'], true)) {
            $operating_mode = 'user';
        }
        $site_name = sanitize_text_field((string) get_option('blogname', get_bloginfo('name')));
        $accent_color = sanitize_hex_color((string) get_option('nexdirectory_style_accent_color', '#4F46E5')) ?: '#4F46E5';
        $starter_blueprints = method_exists($this, 'get_starter_blueprint_catalog')
            ? (array) $this->get_starter_blueprint_catalog()
            : [];
        $active_starter_blueprint = sanitize_key((string) get_option('nexdirectory_active_starter_blueprint', ''));
        $active_site_profile = method_exists($this, 'get_site_profile_key')
            ? sanitize_key((string) $this->get_site_profile_key())
            : 'events';
        if (
            $active_starter_blueprint === ''
            || !isset($starter_blueprints[$active_starter_blueprint])
            || sanitize_key((string) ($starter_blueprints[$active_starter_blueprint]['site_profile'] ?? '')) !== $active_site_profile
        ) {
            $active_starter_blueprint = '';
            foreach ($starter_blueprints as $candidate_blueprint_key => $candidate_blueprint) {
                if (sanitize_key((string) ($candidate_blueprint['site_profile'] ?? '')) === $active_site_profile) {
                    $active_starter_blueprint = sanitize_key((string) $candidate_blueprint_key);
                    break;
                }
            }
        }
        $active_site_profile_config = method_exists($this, 'get_site_profile_config')
            ? (array) $this->get_site_profile_config($active_site_profile)
            : [];
        $active_demo_profile = sanitize_key((string) ($active_site_profile_config['demo_profile'] ?? $active_site_profile));
        if ($active_demo_profile === '') {
            $active_demo_profile = 'events';
        }

        $setup_context = $this->get_onboarding_setup_context($active_site_profile);
        $wizard_steps[1]['is_ready'] = $active_starter_blueprint !== '' || $operating_mode_was_saved;
        $wizard_steps[2]['is_ready'] = !empty($setup_context['core_pages_ready']);
        $wizard_steps[3]['is_ready'] = $site_name !== '' && $accent_color !== '';
        $wizard_steps[4]['is_ready'] = (int) ($setup_context['demo_listing_count'] ?? 0) > 0;
        $wizard_steps[5]['is_ready'] = (int) ($setup_context['homepage_id'] ?? 0) > 0;
        $wizard_steps[6]['is_ready'] = $done;
        if (!empty($wizard_steps[4]['is_ready'])) {
            $wizard_steps[4]['next_label'] = __('Continue to Homepage', 'nexdirectory');
        }
        $setup_ready_count = count(
            array_filter(
                $wizard_steps,
                static function ($wizard_step) {
                    return !empty($wizard_step['is_ready']);
                }
            )
        );
        $active_blueprint_label = '';
        if ($active_starter_blueprint !== '' && isset($starter_blueprints[$active_starter_blueprint])) {
            $active_blueprint_label = sanitize_text_field((string) ($starter_blueprints[$active_starter_blueprint]['label'] ?? ''));
        }

        $onboarding_context = [
            'brand_name' => $brand_name,
            'done' => $done,
            'singular_label' => $singular_label,
            'plural_label' => $plural_label,
            'step' => $step,
            'step_was_requested' => $step_was_requested,
            'wizard_steps' => $wizard_steps,
            'operating_mode' => $operating_mode,
            'site_name' => $site_name,
            'accent_color' => $accent_color,
            'save_preferences_action' => esc_url(admin_url('admin-post.php')),
            'dashboard_url' => esc_url(admin_url('admin.php?page=nexdirectory-dashboard')),
            'starter_blueprints' => $starter_blueprints,
            'active_starter_blueprint' => $active_starter_blueprint,
            'active_blueprint_label' => $active_blueprint_label,
            'active_demo_profile' => $active_demo_profile,
            'setup_ready_count' => $setup_ready_count,
        ];
        $onboarding_markup = (string) $this->render_admin_template(
            'onboarding.php',
            array_merge($onboarding_context, $setup_context)
        );
        echo wp_kses($onboarding_markup, NEXDIRECTORY_Escaping::allowed_html($onboarding_markup));
    }

    public function handle_onboarding_save_preferences() {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Not allowed.', 'nexdirectory'));
        }
        if (!isset($_POST['nexdirectory_onboarding_save_nonce']) || !wp_verify_nonce(sanitize_text_field((string) wp_unslash($_POST['nexdirectory_onboarding_save_nonce'])), 'nexdirectory_onboarding_save_preferences')) {
            wp_die(esc_html__('Invalid request.', 'nexdirectory'));
        }

        $operating_mode = isset($_POST['nexdirectory_onboarding_operating_mode']) ? sanitize_key((string) wp_unslash($_POST['nexdirectory_onboarding_operating_mode'])) : 'user';
        if (!in_array($operating_mode, ['user', 'single'], true)) {
            $operating_mode = 'user';
        }
        update_option('nexdirectory_onboarding_operating_mode', $operating_mode, false);

        $site_name = isset($_POST['nexdirectory_onboarding_site_name']) ? sanitize_text_field((string) wp_unslash($_POST['nexdirectory_onboarding_site_name'])) : '';
        if ($site_name !== '') {
            update_option('blogname', $site_name);
            if (trim((string) get_option('nexdirectory_company_brand_name', '')) === '') {
                update_option('nexdirectory_company_brand_name', $site_name, false);
            }
        }

        $accent_color = isset($_POST['nexdirectory_onboarding_accent_color']) ? sanitize_hex_color((string) wp_unslash($_POST['nexdirectory_onboarding_accent_color'])) : '';
        if (is_string($accent_color) && $accent_color !== '') {
            update_option('nexdirectory_style_accent_color', $accent_color, false);
        }

        $step = isset($_POST['nexdirectory_onboarding_step']) ? absint((int) wp_unslash($_POST['nexdirectory_onboarding_step'])) : 1;
        if ($step < 1 || $step > 6) {
            $step = 1;
        }
        update_option('nexdirectory_onboarding_last_step', $step, false);

        $this->redirect_with_message(
            nexdirectory_add_query_arg(['page' => 'nexdirectory-onboarding', 'step' => $step], admin_url('admin.php')),
            __('Onboarding preferences saved.', 'nexdirectory'),
            'success'
        );
    }

    public function handle_set_onboarding_homepage() {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Not allowed.', 'nexdirectory'));
        }
        if (
            !isset($_POST['nexdirectory_set_onboarding_homepage_nonce'])
            || !wp_verify_nonce(sanitize_text_field((string) wp_unslash($_POST['nexdirectory_set_onboarding_homepage_nonce'])), 'nexdirectory_set_onboarding_homepage')
        ) {
            wp_die(esc_html__('Invalid request.', 'nexdirectory'));
        }

        $step = isset($_POST['nexdirectory_onboarding_step']) ? absint((int) wp_unslash($_POST['nexdirectory_onboarding_step'])) : 5;
        if ($step < 1 || $step > 6) {
            $step = 5;
        }
        $error_redirect_url = nexdirectory_add_query_arg(['page' => 'nexdirectory-onboarding', 'step' => 5], admin_url('admin.php'));

        $whats_new_page_id = $this->set_whats_new_as_site_homepage();
        if ($whats_new_page_id <= 0) {
            $this->redirect_with_message(
                $error_redirect_url,
                __('Could not locate the What\'s New page yet. Generate core pages first, then try again.', 'nexdirectory'),
                'error'
            );
        }

        update_option('nexdirectory_onboarding_last_step', $step, false);
        $success_redirect_url = nexdirectory_add_query_arg(['page' => 'nexdirectory-onboarding', 'step' => $step], admin_url('admin.php'));
        $this->redirect_with_message(
            $success_redirect_url,
            __('Homepage set to What\'s New. Review the launch checklist, then finish setup.', 'nexdirectory'),
            'success'
        );
    }


    public function render_diagnostics_page() {
        if (!current_user_can('manage_options')) {
            return;
        }
        $brand_name = $this->get_admin_brand_name();
        global $wpdb;
        $memory_limit = (string) ini_get('memory_limit');
        $max_execution_time = (string) ini_get('max_execution_time');
        $upload_max_filesize = (string) ini_get('upload_max_filesize');
        $post_max_size = (string) ini_get('post_max_size');
        $cron_next = wp_next_scheduled('nexdirectory_event_reminder_cron');
        $action_scheduler_available = function_exists('as_schedule_single_action') && function_exists('as_next_scheduled_action');
        $action_scheduler_enabled = (string) get_option('nexdirectory_use_action_scheduler', '0') === '1';
        $as_next = '';
        if ($action_scheduler_available && function_exists('as_next_scheduled_action')) {
            $next_action = as_next_scheduled_action('nexdirectory_event_reminder_cron', [], 'nexdirectory');
            if (!empty($next_action)) {
                $as_next = wp_date(get_option('date_format') . ' ' . get_option('time_format'), (int) $next_action);
            }
        }
        $upload_limits = $this->get_upload_limits();
        $rest_url = esc_url_raw(rest_url('nexdirectory/v1/map-events'));
        $postmeta_table = (string) $wpdb->postmeta;
        $posts_table = (string) $wpdb->posts;
        $meta_indexes = $wpdb->get_results("SHOW INDEX FROM {$postmeta_table}", ARRAY_A);
        $post_indexes = $wpdb->get_results("SHOW INDEX FROM {$posts_table}", ARRAY_A);
        $meta_index_names = array_values(array_unique(array_map(static function ($row) {
            return (string) ($row['Key_name'] ?? '');
        }, is_array($meta_indexes) ? $meta_indexes : [])));
        $post_index_names = array_values(array_unique(array_map(static function ($row) {
            return (string) ($row['Key_name'] ?? '');
        }, is_array($post_indexes) ? $post_indexes : [])));
        $plugin_root = plugin_dir_path(NEXDIRECTORY_PLUGIN_FILE);
        $registered_uninstallers = (array) get_option('uninstall_plugins', []);
        $has_uninstall_file = is_readable($plugin_root . 'uninstall.php');
        $has_uninstall_hook = isset($registered_uninstallers[plugin_basename(NEXDIRECTORY_PLUGIN_FILE)]);
        $has_uninstall_handler = $has_uninstall_file || $has_uninstall_hook;
        $demo_limit_context = is_callable([$this, 'get_demo_limit_admin_context']) ? (array) $this->get_demo_limit_admin_context() : [];
        $pro_status_label = !empty($demo_limit_context['pro_license_active'])
            ? __('Pro licensed', 'nexdirectory')
            : __('Included plan', 'nexdirectory');
        $route_options = [
            'nexdirectory_page_events_url' => __('Archive page', 'nexdirectory'),
            'nexdirectory_page_events_map_url' => __('Map page', 'nexdirectory'),
            'nexdirectory_page_submit_url' => __('Submit page', 'nexdirectory'),
            'nexdirectory_page_my_events_url' => __('Workspace page', 'nexdirectory'),
            'nexdirectory_page_whats_new_url' => __('What\'s New page', 'nexdirectory'),
        ];
        $missing_routes = [];
        foreach ($route_options as $option_name => $route_label) {
            $route_url = esc_url_raw((string) get_option($option_name, ''));
            $route_id = $route_url !== '' ? absint((int) url_to_postid($route_url)) : 0;
            if ($route_url === '' || $route_id < 1 || get_post_status($route_id) === false) {
                $missing_routes[] = (string) $route_label;
            }
        }
        $remote_asset_hits = [];
        foreach (['nexdirectory.php', 'includes/trait-nexdirectory-content.php', 'templates/admin/settings/page.php'] as $scan_file) {
            $scan_path = $plugin_root . $scan_file;
            if (!is_readable($scan_path)) {
                continue;
            }
            $scan_contents = (string) file_get_contents($scan_path); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local plugin file diagnostics only.
            if (preg_match_all('#https?://[^\'"\s\)]+#i', $scan_contents, $matches)) {
                foreach ((array) ($matches[0] ?? []) as $match_url) {
                    if (strpos((string) $match_url, home_url('/')) === 0 || strpos((string) $match_url, 'nexdirectory.com') !== false) {
                        continue;
                    }
                    $remote_asset_hits[] = $scan_file . ': ' . esc_url_raw((string) $match_url);
                }
            }
        }
        $readiness_checks = [
            [
                'label' => __('PHP version', 'nexdirectory'),
                'status' => version_compare(PHP_VERSION, '7.4', '>=') ? 'pass' : 'fail',
                'value' => PHP_VERSION,
                'detail' => __('WordPress.org plugins should support modern PHP without fatal errors.', 'nexdirectory'),
            ],
            [
                'label' => __('Frontend routes', 'nexdirectory'),
                'status' => empty($missing_routes) ? 'pass' : 'warn',
                'value' => empty($missing_routes) ? __('All key pages connected', 'nexdirectory') : implode(', ', $missing_routes),
                'detail' => __('Missing route pages cause broken navigation, setup friction, and failed shortcode flows.', 'nexdirectory'),
            ],
            [
                'label' => __('Pro/free status', 'nexdirectory'),
                'status' => 'pass',
                'value' => $pro_status_label,
                'detail' => __('The dashboard should clearly show that publishing is unlimited, with Pro reported separately as an optional add-on.', 'nexdirectory'),
            ],
            [
                'label' => __('Scheduled reminders', 'nexdirectory'),
                'status' => ($cron_next || $as_next !== '') ? 'pass' : 'warn',
                'value' => $cron_next ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), (int) $cron_next) : ($as_next !== '' ? $as_next : __('Not scheduled', 'nexdirectory')),
                'detail' => __('Reminder workflows need either WP-Cron or Action Scheduler available.', 'nexdirectory'),
            ],
            [
                'label' => __('Uninstall behavior', 'nexdirectory'),
                'status' => $has_uninstall_handler ? 'pass' : 'warn',
                'value' => $has_uninstall_file ? __('uninstall.php present', 'nexdirectory') : ($has_uninstall_hook ? __('Cleanup hook registered', 'nexdirectory') : __('No uninstall handler found', 'nexdirectory')),
                'detail' => __('Clean uninstall behavior is an important directory review and user trust point.', 'nexdirectory'),
            ],
            [
                'label' => __('Readme and license files', 'nexdirectory'),
                'status' => (is_readable($plugin_root . 'readme.txt') && is_readable($plugin_root . 'license.txt')) ? 'pass' : 'warn',
                'value' => (is_readable($plugin_root . 'readme.txt') ? __('Readme OK', 'nexdirectory') : __('Readme missing', 'nexdirectory')) . ' / ' . (is_readable($plugin_root . 'license.txt') ? __('License OK', 'nexdirectory') : __('License missing', 'nexdirectory')),
                'detail' => __('WordPress.org submissions need complete plugin metadata and compatible licensing.', 'nexdirectory'),
            ],
            [
                'label' => __('Remote asset scan', 'nexdirectory'),
                'status' => empty($remote_asset_hits) ? 'pass' : 'warn',
                'value' => empty($remote_asset_hits) ? __('No unexpected remote asset URLs in scanned files', 'nexdirectory') : implode('; ', array_slice($remote_asset_hits, 0, 3)),
                'detail' => __('Remote scripts/styles are a common plugin directory rejection risk.', 'nexdirectory'),
            ],
            [
                'label' => __('REST endpoint', 'nexdirectory'),
                'status' => $rest_url !== '' ? 'pass' : 'warn',
                'value' => $rest_url !== '' ? $rest_url : __('Missing', 'nexdirectory'),
                'detail' => __('Map and app screens depend on REST responses being reachable.', 'nexdirectory'),
            ],
        ];
        $check_counts = ['pass' => 0, 'warn' => 0, 'fail' => 0];
        foreach ($readiness_checks as $check) {
            $status = (string) ($check['status'] ?? 'warn');
            if (isset($check_counts[$status])) {
                $check_counts[$status]++;
            }
        }
        ?>
        <div class="wrap nexdirectory-admin-premium nexdirectory-diagnostics-page">
            <?php $this->render_admin_premium_shell_styles(); ?>
            <?php $this->enqueue_local_style_asset('includes-trait-nexdirectory-admin-php-style-5', 'assets/generated/includes-trait-nexdirectory-admin-php-style-5.css'); ?>
            <section class="nexdirectory-admin-hero">
                <span class="nexdirectory-admin-hero-kicker"><?php esc_html_e('Release Readiness', 'nexdirectory'); ?></span>
                <?php /* translators: %s: Placeholder value. */ ?>
                <h1><?php echo esc_html(sprintf(__('%s Diagnostics', 'nexdirectory'), $brand_name)); ?></h1>
                <p><?php esc_html_e('Check environment health, route coverage, Pro status, and WordPress.org submission risks before shipping.', 'nexdirectory'); ?></p>
            </section>
            <section class="nexdirectory-admin-panel">
                <h2><span class="dashicons dashicons-yes-alt" aria-hidden="true"></span><?php esc_html_e('Readiness Checks', 'nexdirectory'); ?></h2>
                <div class="nexdirectory-readiness-summary">
                    <div class="nexdirectory-readiness-count"><strong><?php echo esc_html((string) $check_counts['pass']); ?></strong><span><?php esc_html_e('Passing', 'nexdirectory'); ?></span></div>
                    <div class="nexdirectory-readiness-count"><strong><?php echo esc_html((string) $check_counts['warn']); ?></strong><span><?php esc_html_e('Needs Review', 'nexdirectory'); ?></span></div>
                    <div class="nexdirectory-readiness-count"><strong><?php echo esc_html((string) $check_counts['fail']); ?></strong><span><?php esc_html_e('Must Fix', 'nexdirectory'); ?></span></div>
                </div>
                <div class="nexdirectory-admin-table-wrap">
                    <table class="widefat striped">
                        <thead><tr><th><?php esc_html_e('Status', 'nexdirectory'); ?></th><th><?php esc_html_e('Check', 'nexdirectory'); ?></th><th><?php esc_html_e('Current Value', 'nexdirectory'); ?></th><th><?php esc_html_e('Why It Matters', 'nexdirectory'); ?></th></tr></thead>
                        <tbody>
                            <?php foreach ($readiness_checks as $check) : ?>
                                <?php $status = in_array((string) ($check['status'] ?? ''), ['pass', 'warn', 'fail'], true) ? (string) $check['status'] : 'warn'; ?>
                                <tr>
                                    <td><span class="nexdirectory-check-pill nexdirectory-check-<?php echo esc_attr($status); ?>"><?php echo esc_html($status === 'pass' ? __('OK', 'nexdirectory') : ($status === 'fail' ? __('Fix', 'nexdirectory') : __('Review', 'nexdirectory'))); ?></span></td>
                                    <td><strong><?php echo esc_html((string) ($check['label'] ?? '')); ?></strong></td>
                                    <td><code><?php echo esc_html((string) ($check['value'] ?? '')); ?></code></td>
                                    <td><p class="nexdirectory-readiness-detail"><?php echo esc_html((string) ($check['detail'] ?? '')); ?></p></td>
                                </tr>
                            <?php endforeach; ?>
                        </tbody>
                    </table>
                </div>
            </section>
            <section class="nexdirectory-admin-panel">
            <?php /* translators: %s: Placeholder value. */ ?>
            <h2><span class="dashicons dashicons-admin-tools" aria-hidden="true"></span><?php esc_html_e('Environment Details', 'nexdirectory'); ?></h2>
            <div class="nexdirectory-admin-table-wrap">
            <table class="widefat striped">
                <thead><tr><th><?php esc_html_e('Check', 'nexdirectory'); ?></th><th><?php esc_html_e('Value', 'nexdirectory'); ?></th></tr></thead>
                <tbody>
                    <tr><td><?php esc_html_e('Plugin Version', 'nexdirectory'); ?></td><td><?php echo esc_html((string) self::VERSION); ?></td></tr>
                    <tr><td><?php esc_html_e('DB Schema Version', 'nexdirectory'); ?></td><td><?php echo esc_html((string) NEXDIRECTORY_DB_VERSION); ?></td></tr>
                    <tr><td><?php esc_html_e('WordPress Version', 'nexdirectory'); ?></td><td><?php echo esc_html((string) get_bloginfo('version')); ?></td></tr>
                    <tr><td><?php esc_html_e('PHP Version', 'nexdirectory'); ?></td><td><?php echo esc_html((string) PHP_VERSION); ?></td></tr>
                    <tr><td><?php esc_html_e('Memory Limit', 'nexdirectory'); ?></td><td><?php echo esc_html($memory_limit); ?></td></tr>
                    <tr><td><?php esc_html_e('Max Execution Time', 'nexdirectory'); ?></td><td><?php echo esc_html($max_execution_time . 's'); ?></td></tr>
                    <tr><td><?php esc_html_e('Upload Max Filesize', 'nexdirectory'); ?></td><td><?php echo esc_html($upload_max_filesize); ?></td></tr>
                    <tr><td><?php esc_html_e('Post Max Size', 'nexdirectory'); ?></td><td><?php echo esc_html($post_max_size); ?></td></tr>
                    <tr><td><?php esc_html_e('Configured Cover Upload Limit', 'nexdirectory'); ?></td><td><?php echo esc_html((string) ((int) ($upload_limits['cover_max_kb'] ?? 500)) . ' KB'); ?></td></tr>
                    <tr><td><?php esc_html_e('Configured Gallery Upload Limit', 'nexdirectory'); ?></td><td><?php echo esc_html((string) ((int) ($upload_limits['gallery_max_kb'] ?? 2000)) . ' KB'); ?></td></tr>
                    <tr><td><?php esc_html_e('Configured Gallery Max Files', 'nexdirectory'); ?></td><td><?php echo esc_html((string) ((int) ($upload_limits['gallery_max_files'] ?? 10))); ?></td></tr>
                    <tr><td><?php esc_html_e('Reminder Cron Next Run', 'nexdirectory'); ?></td><td><?php echo esc_html($cron_next ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), (int) $cron_next) : __('Not scheduled', 'nexdirectory')); ?></td></tr>
                    <tr><td><?php esc_html_e('Action Scheduler Available', 'nexdirectory'); ?></td><td><?php echo esc_html($action_scheduler_available ? __('Yes', 'nexdirectory') : __('No', 'nexdirectory')); ?></td></tr>
                    <tr><td><?php esc_html_e('Action Scheduler Enabled', 'nexdirectory'); ?></td><td><?php echo esc_html($action_scheduler_enabled ? __('Yes', 'nexdirectory') : __('No', 'nexdirectory')); ?></td></tr>
                    <tr><td><?php esc_html_e('Action Scheduler Next Reminder Run', 'nexdirectory'); ?></td><td><?php echo esc_html($as_next !== '' ? $as_next : __('Not scheduled', 'nexdirectory')); ?></td></tr>
                    <tr><td><?php esc_html_e('Uninstall Data Purge', 'nexdirectory'); ?></td><td><?php echo esc_html((string) get_option('nexdirectory_uninstall_remove_data', '0') === '1' ? __('Enabled', 'nexdirectory') : __('Disabled', 'nexdirectory')); ?></td></tr>
                    <tr><td><?php esc_html_e('REST Endpoint', 'nexdirectory'); ?></td><td><code><?php echo esc_html($rest_url); ?></code></td></tr>
                    <tr><td><?php esc_html_e('Meta Indexes', 'nexdirectory'); ?></td><td><code><?php echo esc_html(implode(', ', $meta_index_names)); ?></code></td></tr>
                    <tr><td><?php esc_html_e('Posts Indexes', 'nexdirectory'); ?></td><td><code><?php echo esc_html(implode(', ', $post_index_names)); ?></code></td></tr>
                </tbody>
            </table>
            </div>
            <p><?php esc_html_e('If moderation/import/map performance is degraded, check index rows above and review runtime logs under Settings > Logs.', 'nexdirectory'); ?></p>
            </section>
        </div>
        <?php
    }    private function find_user_for_owner_transfer($identifier = '') {
        $identifier = sanitize_text_field((string) $identifier);
        if ($identifier === '') {
            return null;
        }
        if (is_numeric($identifier)) {
            $candidate = get_user_by('id', absint((int) $identifier));
            if ($candidate instanceof WP_User) {
                return $candidate;
            }
        }
        if (is_email($identifier)) {
            $candidate = get_user_by('email', sanitize_email($identifier));
            if ($candidate instanceof WP_User) {
                return $candidate;
            }
        }
        $candidate = get_user_by('login', $identifier);
        if ($candidate instanceof WP_User) {
            return $candidate;
        }
        $candidate = get_user_by('slug', nexdirectory_sanitize_title($identifier));
        if ($candidate instanceof WP_User) {
            return $candidate;
        }
        return null;
    }


    private function get_transfer_owner_admin_url($post_id) {
        $post_id = absint((int) $post_id);
        return (string) wp_nonce_url(nexdirectory_add_query_arg([
            'page' => 'nexdirectory-transfer-owner',
            'post_id' => $post_id,
        ], admin_url('admin.php')), 'nexdirectory_transfer_owner', 'nexdirectory_transfer_owner_nonce');
    }


    public function handle_open_transfer_owner() {
        if (!is_admin() || !is_user_logged_in() || !current_user_can('manage_options')) {
            wp_die(esc_html__('Not allowed.', 'nexdirectory'));
        }

        check_admin_referer('nexdirectory_transfer_owner', 'nexdirectory_transfer_owner_nonce');

        $post_id = filter_input(INPUT_GET, 'post_id', FILTER_VALIDATE_INT);
        $post_id = $post_id ? absint((int) $post_id) : 0;
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            wp_die(esc_html__('Invalid listing selected.', 'nexdirectory'));
        }

        wp_safe_redirect($this->get_transfer_owner_admin_url($post_id));
        exit;
    }


    public function render_transfer_owner_page() {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Not allowed.', 'nexdirectory'));
        }
        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Transfer owner page reads query parameters only to choose the listing context.
        $post_id = isset($_GET['post_id']) ? absint((int) wp_unslash($_GET['post_id'])) : 0;
        if ($post_id <= 0) {
            $post_id = isset($_GET['post']) ? absint((int) wp_unslash($_GET['post'])) : 0;
        }
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            wp_die(esc_html__('Invalid listing selected.', 'nexdirectory'));
        }

        $post = get_post($post_id);
        if (!$post instanceof WP_Post) {
            wp_die(esc_html__('Listing not found.', 'nexdirectory'));
        }
        $owner_id = (int) $post->post_author;
        $owner_user = $owner_id > 0 ? get_user_by('id', $owner_id) : null;
        $owner_name = $owner_user instanceof WP_User
            ? (string) ($owner_user->display_name ?: $owner_user->user_login)
            : ('#' . (string) $owner_id);
        $owner_email = $owner_user instanceof WP_User ? (string) $owner_user->user_email : '';
        $status = (string) get_post_status($post_id);
        $edit_frontend_url = nexdirectory_add_query_arg('edit_event_id', $post_id, $this->get_platform_page_url(
            'nexdirectory_page_submit_event_url',
            $this->get_profile_route_slugs('submit'),
            $this->get_profile_route_default_url('submit')
        ));
        $edit_backend_url = nexdirectory_add_query_arg([
            'post' => $post_id,
            'action' => 'edit',
            'nexdirectory_wp_editor' => '1',
        ], admin_url('post.php'));
        $suggested_users = get_users([
            'number' => 120,
            'orderby' => 'display_name',
            'order' => 'ASC',
            'fields' => ['ID', 'display_name', 'user_login', 'user_email'],
        ]);
        ?>
        <div class="wrap">
            <h1><?php esc_html_e('Change Listing Owner', 'nexdirectory'); ?></h1>
            <?php echo wp_kses_post((string) $this->render_notice()); ?>
            <div class="card" style="max-width:980px;padding:18px;">
                <h2 style="margin-top:0;"><?php echo esc_html((string) get_the_title($post_id)); ?></h2>
                <p>
                    <strong><?php esc_html_e('Current Owner:', 'nexdirectory'); ?></strong>
                    <?php echo esc_html($owner_name); ?>
                    <?php if ($owner_email !== '') : ?>
                        (<?php echo esc_html($owner_email); ?>)
                    <?php endif; ?>
                </p>
                <p>
                    <strong><?php esc_html_e('Status:', 'nexdirectory'); ?></strong>
                    <?php echo esc_html(ucfirst($status)); ?>
                </p>
                <p style="display:flex;gap:10px;flex-wrap:wrap;">
                    <a class="button" href="<?php echo esc_url((string) $edit_frontend_url); ?>"><?php esc_html_e('Open Frontend Edit Form', 'nexdirectory'); ?></a>
                    <a class="button" href="<?php echo esc_url((string) $edit_backend_url); ?>"><?php esc_html_e('Edit WP Page', 'nexdirectory'); ?></a>
                </p>
            </div>

            <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" class="card" style="max-width:980px;padding:18px;">
                <input type="hidden" name="action" value="nexdirectory_reassign_listing_owner">
                <input type="hidden" name="post_id" value="<?php echo esc_attr((string) $post_id); ?>">
                <input type="hidden" name="redirect_to" value="<?php echo esc_attr($this->get_transfer_owner_admin_url($post_id)); ?>">
                <?php wp_nonce_field('nexdirectory_reassign_listing_owner', 'nexdirectory_reassign_listing_owner_nonce'); ?>

                <table class="form-table" role="presentation">
                    <tr>
                        <th scope="row"><label for="nexdirectory_new_owner_identifier"><?php esc_html_e('New Owner', 'nexdirectory'); ?></label></th>
                        <td>
                            <select id="nexdirectory_new_owner_identifier" name="new_owner_identifier" class="regular-text" required>
                                <option value=""><?php esc_html_e('Select a user...', 'nexdirectory'); ?></option>
                                <?php foreach ((array) $suggested_users as $user) : ?>
                                    <?php
                                    if ($user instanceof WP_User) {
                                        $user_id = (int) $user->ID;
                                        $display_name = (string) ($user->display_name ?: $user->user_login);
                                        $user_email = (string) $user->user_email;
                                    } elseif (is_object($user)) {
                                        $user_id = (int) ($user->ID ?? 0);
                                        $display_name = (string) (($user->display_name ?? '') !== '' ? $user->display_name : ($user->user_login ?? ''));
                                        $user_email = (string) ($user->user_email ?? '');
                                    } else {
                                        continue;
                                    }
                                    if ($user_id <= 0) {
                                        continue;
                                    }
                                    ?>
                                    <option value="<?php echo esc_attr((string) $user_id); ?>" <?php selected($owner_id, $user_id); ?>><?php echo esc_html($display_name . ' (' . $user_email . ')'); ?></option>
                                <?php endforeach; ?>
                            </select>
                            <p class="description"><?php esc_html_e('Select the user who should become the new owner.', 'nexdirectory'); ?></p>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row"><?php esc_html_e('Notify New Owner', 'nexdirectory'); ?></th>
                        <td>
                            <label><input type="checkbox" name="notify_new_owner" value="1" checked> <?php esc_html_e('Send an email notification to the new owner', 'nexdirectory'); ?></label>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row"><label for="nexdirectory_new_owner_subject"><?php esc_html_e('Email Subject (optional)', 'nexdirectory'); ?></label></th>
                        <td><input type="text" id="nexdirectory_new_owner_subject" name="notify_subject" class="regular-text" placeholder="<?php esc_attr_e('You are now the owner of a listing', 'nexdirectory'); ?>"></td>
                    </tr>
                    <tr>
                        <th scope="row"><label for="nexdirectory_new_owner_message"><?php esc_html_e('Email Message (optional)', 'nexdirectory'); ?></label></th>
                        <td>
                            <textarea id="nexdirectory_new_owner_message" name="notify_message" rows="5" class="large-text" placeholder="<?php esc_attr_e('Optional custom note for the new owner.', 'nexdirectory'); ?>"></textarea>
                            <p class="description"><?php esc_html_e('If left blank, a default ownership transfer message is sent.', 'nexdirectory'); ?></p>
                        </td>
                    </tr>
                </table>

                <p>
                    <button type="submit" class="button button-primary"><?php esc_html_e('Change Owner', 'nexdirectory'); ?></button>
                    <a class="button" href="<?php echo esc_url(nexdirectory_add_query_arg(['post_type' => self::POST_TYPE, 'mode' => 'list'], admin_url('edit.php'))); ?>"><?php esc_html_e('Back to Listings', 'nexdirectory'); ?></a>
                </p>
            </form>
        </div>
        <?php
    }


    public function handle_reassign_listing_owner() {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Not allowed.', 'nexdirectory'));
        }
        check_admin_referer('nexdirectory_reassign_listing_owner', 'nexdirectory_reassign_listing_owner_nonce');
        $post_id = isset($_POST['post_id']) ? absint((int) wp_unslash($_POST['post_id'])) : 0;
        $redirect_to = isset($_POST['redirect_to']) ? esc_url_raw((string) wp_unslash($_POST['redirect_to'])) : '';
        if ($redirect_to === '') {
            $redirect_to = $this->get_transfer_owner_admin_url($post_id);
        }
        if ($post_id <= 0 || get_post_type($post_id) !== self::POST_TYPE) {
            $this->safe_redirect_or_fallback(nexdirectory_add_query_arg([
                'nexdirectory_notice' => rawurlencode(__('Invalid listing selected.', 'nexdirectory')),
                'nexdirectory_notice_type' => 'error',
            ], $redirect_to));
        }
        $identifier = sanitize_text_field((string) wp_unslash($_POST['new_owner_identifier'] ?? ''));
        $new_owner = $this->find_user_for_owner_transfer($identifier);
        if (!$new_owner instanceof WP_User) {
            $this->safe_redirect_or_fallback(nexdirectory_add_query_arg([
                'nexdirectory_notice' => rawurlencode(__('Could not find a user for that owner value.', 'nexdirectory')),
                'nexdirectory_notice_type' => 'error',
            ], $redirect_to));
        }
        $current_owner_id = (int) get_post_field('post_author', $post_id);
        if ($current_owner_id === (int) $new_owner->ID) {
            $this->safe_redirect_or_fallback(nexdirectory_add_query_arg([
                'nexdirectory_notice' => rawurlencode(__('This user is already the listing owner.', 'nexdirectory')),
                'nexdirectory_notice_type' => 'error',
            ], $redirect_to));
        }

        $updated = wp_update_post([
            'ID' => $post_id,
            'post_author' => (int) $new_owner->ID,
        ], true);
        if (is_wp_error($updated)) {
            $this->safe_redirect_or_fallback(nexdirectory_add_query_arg([
                'nexdirectory_notice' => rawurlencode((string) $updated->get_error_message()),
                'nexdirectory_notice_type' => 'error',
            ], $redirect_to));
        }
        $notify_sent = false;
        $should_notify = isset($_POST['notify_new_owner']) && sanitize_key((string) wp_unslash($_POST['notify_new_owner'])) === '1';
        if ($should_notify && is_email((string) $new_owner->user_email)) {
            $subject = sanitize_text_field((string) wp_unslash($_POST['notify_subject'] ?? ''));
            if ($subject === '') {
                /* translators: %s: Placeholder value. */
                $subject = sprintf(__('Listing ownership transferred: %s', 'nexdirectory'), (string) get_the_title($post_id));
            }
            $custom_message = sanitize_textarea_field((string) wp_unslash($_POST['notify_message'] ?? ''));
            if ($custom_message === '') {
                $custom_message = sprintf(
                    /* translators: %1$s: Placeholder value. */
                    __('You have been assigned as the owner of "%1$s". You can now manage it from your account dashboard.', 'nexdirectory'),
                    (string) get_the_title($post_id)
                );
            }
            $frontend_edit_url = nexdirectory_add_query_arg('edit_event_id', $post_id, $this->get_platform_page_url(
                'nexdirectory_page_submit_event_url',
                $this->get_profile_route_slugs('submit'),
                $this->get_profile_route_default_url('submit')
            ));
            $headers = [];
            $admin_user = wp_get_current_user();
            if ($admin_user instanceof WP_User && is_email((string) $admin_user->user_email)) {
                $headers[] = 'Reply-To: ' . $admin_user->display_name . ' <' . $admin_user->user_email . '>';
            }
            $fallback_text = sprintf(
                "Hello %s,\n\n%s\n\nListing: %s\nPublic URL: %s\nEdit Form: %s",
                (string) ($new_owner->display_name ?: $new_owner->user_login),
                $custom_message,
                (string) get_the_title($post_id),
                (string) get_permalink($post_id),
                (string) $frontend_edit_url
            );
            $notify_sent = $this->send_platform_email((string) $new_owner->user_email, $subject, '', [
                'fallback_text' => $fallback_text,
            ], $headers);
        }

        $notice = $should_notify
            ? ($notify_sent
                ? __('Listing owner updated and notification email sent.', 'nexdirectory')
                : __('Listing owner updated, but email could not be sent.', 'nexdirectory'))
            : __('Listing owner updated.', 'nexdirectory');
        $notice_type = (!$should_notify || $notify_sent) ? 'success' : 'error';

        $this->safe_redirect_or_fallback(nexdirectory_add_query_arg([
            'nexdirectory_notice' => rawurlencode($notice),
            'nexdirectory_notice_type' => $notice_type,
        ], $redirect_to));
    }


    public function render_import_page() {
        if (!current_user_can('manage_options')) {
            return;
        }
        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Import page reads preview tokens only to display the current preview.
        $preview_token = isset($_GET['nexdirectory_import_preview']) ? sanitize_key(wp_unslash($_GET['nexdirectory_import_preview'])) : '';
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
        $preview_data = null;
        if ($preview_token !== '') {
            $payload = get_transient($this->get_import_preview_transient_key($preview_token));
            if (is_array($payload) && (int) ($payload['user_id'] ?? 0) === get_current_user_id()) {
                $preview_data = $payload;
            }
        }
        $supported_fields = $this->get_import_supported_fields();
        $demo_profiles = method_exists($this, 'get_public_demo_profile_catalog') ? (array) $this->get_public_demo_profile_catalog() : [];
        if (empty($demo_profiles)) {
            $demo_profiles = [
                'events' => ['label' => __('Events', 'nexdirectory')],
            ];
        }
        $requested_demo_profile = method_exists($this, 'get_demo_profile_key_for_platform_mode')
            ? sanitize_key((string) $this->get_demo_profile_key_for_platform_mode())
            : 'events';
        $default_demo_profile = method_exists($this, 'resolve_demo_import_profile_key')
            ? sanitize_key((string) $this->resolve_demo_import_profile_key($requested_demo_profile))
            : 'events';
        if ($default_demo_profile === '' || !isset($demo_profiles[$default_demo_profile])) {
            $default_demo_profile = array_key_first($demo_profiles);
        }
        if (!is_string($default_demo_profile) || $default_demo_profile === '') {
            $default_demo_profile = 'events';
        }
        $ui_labels = method_exists($this, 'get_site_profile_ui_labels') ? (array) $this->get_site_profile_ui_labels() : [];
        $singular_label = sanitize_text_field((string) ($ui_labels['singular'] ?? __('Listing', 'nexdirectory')));
        $plural_label = sanitize_text_field((string) ($ui_labels['plural'] ?? __('Listings', 'nexdirectory')));
        $plural_lower = sanitize_text_field((string) ($ui_labels['plural_lower'] ?? strtolower((string) $plural_label)));
        ?>
        <div class="wrap nexdirectory-import-page">
            <?php $this->enqueue_local_style_asset('includes-trait-nexdirectory-admin-php-style-6', 'assets/generated/includes-trait-nexdirectory-admin-php-style-6.css'); ?>

            <section class="nexdirectory-import-hero">
                <h1><?php esc_html_e('Import Listing', 'nexdirectory'); ?></h1>
                <p><?php esc_html_e('Upload CSV or JSON data, preview column mapping, and import safely with duplicate controls and status handling.', 'nexdirectory'); ?></p>
            </section>

            <?php echo wp_kses_post((string) $this->render_notice()); ?>

            <section class="nexdirectory-import-toolbar">
                <form class="nexdirectory-import-card" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
                    <h2><span class="dashicons dashicons-database-export"></span><?php esc_html_e('Export Listings', 'nexdirectory'); ?></h2>
                    <?php /* translators: %s: Placeholder value. */ ?>
                    <p><?php echo esc_html(sprintf(__('Download all existing %s in CSV or JSON.', 'nexdirectory'), strtolower((string) $plural_label))); ?></p>
                    <input type="hidden" name="action" value="nexdirectory_export_events">
                    <?php wp_nonce_field('nexdirectory_export_events_nonce', 'nexdirectory_export_nonce'); ?>
                    <div class="nexdirectory-import-field-grid">
                        <div>
                            <label for="nexdirectory_export_format"><?php esc_html_e('Format', 'nexdirectory'); ?></label>
                            <select id="nexdirectory_export_format" name="nexdirectory_export_format">
                                <option value="csv"><?php esc_html_e('CSV', 'nexdirectory'); ?></option>
                                <option value="json"><?php esc_html_e('JSON', 'nexdirectory'); ?></option>
                            </select>
                        </div>
                        <div><?php submit_button(__('Export Data', 'nexdirectory'), 'button-secondary', 'submit', false); ?></div>
                    </div>
                </form>

                <form class="nexdirectory-import-card" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
                    <h2><span class="dashicons dashicons-media-spreadsheet"></span><?php esc_html_e('Download Demo Template', 'nexdirectory'); ?></h2>
                    <p><?php esc_html_e('Get a ready-to-fill template that matches supported import fields.', 'nexdirectory'); ?></p>
                    <input type="hidden" name="action" value="nexdirectory_export_demo_events">
                    <?php wp_nonce_field('nexdirectory_export_events_nonce', 'nexdirectory_export_nonce'); ?>
                    <div class="nexdirectory-import-field-grid">
                        <div>
                            <label for="nexdirectory_export_demo_format"><?php esc_html_e('Format', 'nexdirectory'); ?></label>
                            <select id="nexdirectory_export_demo_format" name="nexdirectory_export_format">
                                <option value="csv"><?php esc_html_e('CSV', 'nexdirectory'); ?></option>
                                <option value="json"><?php esc_html_e('JSON', 'nexdirectory'); ?></option>
                            </select>
                        </div>
                        <div><?php submit_button(__('Download Template', 'nexdirectory'), 'button-secondary', 'submit', false); ?></div>
                    </div>
                </form>

                <form
                    class="nexdirectory-import-card nexdirectory-demo-import-form"
                    method="post"
                    action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
                    data-nexdirectory-demo-import-confirm-form
                    data-confirm-message="<?php esc_attr_e('This will import sample listings and bundled demo images into this site. Continue?', 'nexdirectory'); ?>"
                >
                    <h2><span class="dashicons dashicons-database-add"></span><?php esc_html_e('Import Demo Data', 'nexdirectory'); ?></h2>
                    <p><?php esc_html_e('Generate realistic sample listings with matching taxonomy and layout presets.', 'nexdirectory'); ?></p>
                    <p class="description"><?php esc_html_e('Sample entries are fully editable.', 'nexdirectory'); ?></p>
                    <input type="hidden" name="action" value="nexdirectory_import_demo_events">
                    <input type="hidden" name="nexdirectory_redirect_to" value="<?php echo esc_url(home_url('/')); ?>">
                    <input type="hidden" name="nexdirectory_demo_import_confirmed" value="0" data-nexdirectory-demo-import-confirmed-input>
                    <?php wp_nonce_field('nexdirectory_import_demo_events_nonce', 'nexdirectory_import_demo_nonce'); ?>
                    <div class="nexdirectory-import-field-grid">
                        <div>
                            <label for="nexdirectory_demo_profile"><?php esc_html_e('Demo Profile', 'nexdirectory'); ?></label>
                            <select id="nexdirectory_demo_profile" name="nexdirectory_demo_profile">
                                <?php foreach ($demo_profiles as $demo_profile_key => $demo_profile_config) : ?>
                                    <?php
                                    $demo_profile_key = sanitize_key((string) $demo_profile_key);
                                    if ($demo_profile_key === '') {
                                        continue;
                                    }
                                    $demo_profile_label = sanitize_text_field((string) ($demo_profile_config['label'] ?? $demo_profile_key));
                                    ?>
                                    <option value="<?php echo esc_attr($demo_profile_key); ?>" <?php selected($default_demo_profile, $demo_profile_key); ?>><?php echo esc_html($demo_profile_label); ?></option>
                                <?php endforeach; ?>
                            </select>
                            <p class="description"><?php esc_html_e('Choose matching sample content for Events, Directory, Jobs, or Marketplace sites.', 'nexdirectory'); ?></p>
                        </div>
                        <input type="hidden" name="nexdirectory_demo_count" value="12">
                        <div class="nexdirectory-import-field-grid" style="grid-column: 1 / -1;">
                            <div>
                                <input type="hidden" name="nexdirectory_demo_apply_presets" value="0">
                                <label style="display:flex;align-items:center;gap:6px;margin-bottom:0;">
                                    <input type="checkbox" name="nexdirectory_demo_apply_presets" value="1" checked>
                                    <?php esc_html_e('Apply profile design and taxonomy presets', 'nexdirectory'); ?>
                                </label>
                            </div>
                            <div>
                                <input type="hidden" name="nexdirectory_demo_localize_media" value="0">
                                <label style="display:flex;align-items:center;gap:6px;margin-bottom:0;">
                                    <input type="checkbox" name="nexdirectory_demo_localize_media" value="1" checked>
                                    <?php esc_html_e('Copy bundled demo images into Media Library', 'nexdirectory'); ?>
                                </label>
                            </div>
                            <div>
                                <input type="hidden" name="nexdirectory_demo_seed_runtime" value="0">
                                <label style="display:flex;align-items:center;gap:6px;margin-bottom:0;">
                                    <input type="checkbox" name="nexdirectory_demo_seed_runtime" value="1">
                                    <?php esc_html_e('Seed runtime activity (analytics, chat, tickets) (slower)', 'nexdirectory'); ?>
                                </label>
                            </div>
                            <div>
                                <button type="submit" class="button button-primary">
                                    <?php esc_html_e('Import Demo Data', 'nexdirectory'); ?>
                                </button>
                            </div>
                        </div>
                    </div>
                    <?php /* translators: %s: Placeholder value. */ ?>
                    <p class="description"><?php echo esc_html(sprintf(__('Profiles change sample %s types/tags plus map and single-page layout presets.', 'nexdirectory'), $plural_lower)); ?></p>
                </form>
            </section>

            <div class="nexdirectory-import-loading" id="nexdirectory-demo-import-loading" aria-hidden="true">
                <div class="nexdirectory-import-loading-backdrop"></div>
                <div class="nexdirectory-import-loading-card" role="status" aria-live="polite">
                    <div class="nexdirectory-import-loading-icon">
                        <span class="nexdirectory-import-loading-spinner"></span>
                    </div>
                    <h3><?php esc_html_e('Importing Demo Data', 'nexdirectory'); ?></h3>
                    <p><?php esc_html_e('Please keep this tab open. This can take a minute.', 'nexdirectory'); ?></p>
                </div>
            </div>

            <?php $this->enqueue_local_script_asset('includes-trait-nexdirectory-admin-php-script-1', 'assets/generated/includes-trait-nexdirectory-admin-php-script-1.js'); ?>

            <?php if ($preview_data) : ?>
                <?php
                $headers = array_values(array_filter(array_map('strval', (array) ($preview_data['headers'] ?? []))));
                $sample_rows = array_slice((array) ($preview_data['sample_rows'] ?? []), 0, 5);
                $mapping = is_array($preview_data['mapping'] ?? null) ? (array) $preview_data['mapping'] : [];
                $default_status = sanitize_key((string) ($preview_data['default_status'] ?? 'pending'));
                if (!in_array($default_status, ['pending', 'draft', 'publish'], true)) {
                    $default_status = 'pending';
                }
                $skip_duplicates = (string) ($preview_data['skip_duplicates'] ?? '1') === '1';
                ?>
                <section class="nexdirectory-import-panel">
                    <h2><span class="dashicons dashicons-visibility"></span><?php esc_html_e('Step 2: Preview and Column Mapping', 'nexdirectory'); ?></h2>
                    <p>
                        <?php
                        echo esc_html(
                            sprintf(
                                /* translators: 1: Placeholder value 1, 2: Placeholder value 2. */
                                __('Detected %1$d rows. Map your source columns to %2$s fields before import.', 'nexdirectory'),
                                (int) ($preview_data['row_count'] ?? 0),
                                strtolower((string) $singular_label)
                            )
                        );
                        ?>
                    </p>
                    <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
                        <input type="hidden" name="action" value="nexdirectory_import_events">
                        <input type="hidden" name="nexdirectory_import_step" value="apply">
                        <input type="hidden" name="nexdirectory_import_token" value="<?php echo esc_attr($preview_token); ?>">
                        <?php wp_nonce_field('nexdirectory_import_events_nonce', 'nexdirectory_import_nonce'); ?>
                        <table class="form-table nexdirectory-import-form-table" role="presentation">
                            <tr>
                                <th scope="row"><label for="nexdirectory_import_default_status_apply"><?php esc_html_e('Default Status', 'nexdirectory'); ?></label></th>
                                <td>
                                    <select id="nexdirectory_import_default_status_apply" name="nexdirectory_import_default_status">
                                        <option value="pending" <?php selected($default_status, 'pending'); ?>><?php esc_html_e('Pending', 'nexdirectory'); ?></option>
                                        <option value="draft" <?php selected($default_status, 'draft'); ?>><?php esc_html_e('Draft', 'nexdirectory'); ?></option>
                                        <option value="publish" <?php selected($default_status, 'publish'); ?>><?php esc_html_e('Published', 'nexdirectory'); ?></option>
                                    </select>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Duplicate Handling', 'nexdirectory'); ?></th>
                                <td>
                                    <label style="display:flex;align-items:center;gap:6px;margin-bottom:0;">
                                        <input type="checkbox" name="nexdirectory_import_skip_duplicates" value="1" <?php checked($skip_duplicates); ?>>
                                        <?php esc_html_e('Skip rows where title + start datetime already exist', 'nexdirectory'); ?>
                                    </label>
                                </td>
                            </tr>
                        </table>
                        <h3><?php esc_html_e('Map Source Columns', 'nexdirectory'); ?></h3>
                        <div class="nexdirectory-import-table-wrap">
                            <table class="widefat striped nexdirectory-import-table">
                                <thead>
                                    <tr>
                                        <?php /* translators: %s: Placeholder value. */ ?>
                                        <th><?php echo esc_html(sprintf(__('%s Field', 'nexdirectory'), $singular_label)); ?></th>
                                        <th><?php esc_html_e('Source Column', 'nexdirectory'); ?></th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <?php foreach ($supported_fields as $target_field => $label) : ?>
                                        <?php $selected_header = sanitize_key((string) ($mapping[$target_field] ?? '')); ?>
                                        <tr>
                                            <td><code><?php echo esc_html($target_field); ?></code> - <?php echo esc_html($label); ?></td>
                                            <td>
                                                <select name="nexdirectory_import_map[<?php echo esc_attr($target_field); ?>]">
                                                    <option value=""><?php esc_html_e('Ignore', 'nexdirectory'); ?></option>
                                                    <?php foreach ($headers as $header) : ?>
                                                        <option value="<?php echo esc_attr($header); ?>" <?php selected($selected_header, sanitize_key($header)); ?>>
                                                            <?php echo esc_html($header); ?>
                                                        </option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                </tbody>
                            </table>
                        </div>
                        <div class="nexdirectory-import-form-actions">
                            <?php submit_button(__('Run Import', 'nexdirectory'), 'primary', 'submit', false); ?>
                            <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=nexdirectory-import')); ?>"><?php esc_html_e('Start Over', 'nexdirectory'); ?></a>
                        </div>
                    </form>
                    <h3><?php esc_html_e('Preview (First 5 Rows)', 'nexdirectory'); ?></h3>
                    <?php if (!empty($headers) && !empty($sample_rows)) : ?>
                        <div class="nexdirectory-import-table-wrap">
                            <table class="widefat striped nexdirectory-import-table">
                                <thead>
                                    <tr>
                                        <?php foreach ($headers as $header) : ?>
                                            <th><?php echo esc_html($header); ?></th>
                                        <?php endforeach; ?>
                                    </tr>
                                </thead>
                                <tbody>
                                    <?php foreach ($sample_rows as $sample_row) : ?>
                                        <tr>
                                            <?php foreach ($headers as $header) : ?>
                                                <?php $cell = isset($sample_row[$header]) ? (string) $sample_row[$header] : ''; ?>
                                                <td><?php echo esc_html(wp_html_excerpt($cell, 120, '...')); ?></td>
                                            <?php endforeach; ?>
                                        </tr>
                                    <?php endforeach; ?>
                                </tbody>
                            </table>
                        </div>
                    <?php else : ?>
                        <p><?php esc_html_e('No preview rows available.', 'nexdirectory'); ?></p>
                    <?php endif; ?>
                </section>
            <?php else : ?>
                <section class="nexdirectory-import-panel">
                    <h2><span class="dashicons dashicons-upload"></span><?php esc_html_e('Step 1: Upload Source File', 'nexdirectory'); ?></h2>
                    <p><?php esc_html_e('Upload a JSON array or CSV file. Step 2 will let you preview rows and map columns before import.', 'nexdirectory'); ?></p>
                    <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" enctype="multipart/form-data">
                        <input type="hidden" name="action" value="nexdirectory_import_events">
                        <input type="hidden" name="nexdirectory_import_step" value="preview">
                        <?php wp_nonce_field('nexdirectory_import_events_nonce', 'nexdirectory_import_nonce'); ?>
                        <table class="form-table nexdirectory-import-form-table" role="presentation">
                            <tr>
                                <th scope="row"><label for="nexdirectory_import_file"><?php esc_html_e('Import File', 'nexdirectory'); ?></label></th>
                                <td><input type="file" id="nexdirectory_import_file" name="nexdirectory_import_file" accept=".json,.csv,text/csv,application/json" required></td>
                            </tr>
                            <tr>
                                <th scope="row"><label for="nexdirectory_import_default_status"><?php esc_html_e('Default Status', 'nexdirectory'); ?></label></th>
                                <td>
                                    <select id="nexdirectory_import_default_status" name="nexdirectory_import_default_status">
                                        <option value="pending"><?php esc_html_e('Pending', 'nexdirectory'); ?></option>
                                        <option value="draft"><?php esc_html_e('Draft', 'nexdirectory'); ?></option>
                                        <option value="publish"><?php esc_html_e('Published', 'nexdirectory'); ?></option>
                                    </select>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Duplicate Handling', 'nexdirectory'); ?></th>
                                <td>
                                    <label style="display:flex;align-items:center;gap:6px;margin-bottom:0;">
                                        <input type="checkbox" name="nexdirectory_import_skip_duplicates" value="1" checked>
                                        <?php esc_html_e('Skip rows where title + start datetime already exist', 'nexdirectory'); ?>
                                    </label>
                                </td>
                            </tr>
                        </table>
                        <?php submit_button(__('Continue To Preview', 'nexdirectory')); ?>
                    </form>
                </section>
            <?php endif; ?>

            <section class="nexdirectory-import-panel nexdirectory-import-supported">
                <h2><span class="dashicons dashicons-editor-ul"></span><?php esc_html_e('Supported Fields', 'nexdirectory'); ?></h2>
                <p><?php esc_html_e('Use these keys in your CSV header row or JSON objects.', 'nexdirectory'); ?></p>
                <code>title,description,excerpt,start,end,timezone,venue,address,city,country,lat,lng,capacity,price,external_url,contact_email,contact_phone,contact_name,cover_image_url,official_event,recurrence,recurrence_until,video_urls,gallery_urls,sessions,event_type,event_mode,event_price_range,event_tags,status</code>
                <p><?php esc_html_e('Use comma, pipe, or newline separated values for tag/type/mode/price lists and media URLs.', 'nexdirectory'); ?></p>
            </section>
        </div>
        <?php
    }

}
