<?php
/**
 * Admin Orchestrator
 * Manages admin menu, AJAX handlers, and asset enqueuing
 */

class Claude_AI_Copywriter_Admin {

    /**
     * Initialize admin functionality
     */
    public function init() {
        add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
        add_action( 'admin_head', array( $this, 'output_global_admin_head' ) );
        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
        add_action( 'admin_notices', array( $this, 'display_notices' ) );
        add_filter( 'admin_body_class', array( $this, 'add_kc_body_class' ) );
        add_action( 'admin_footer', array( $this, 'output_onboarding_wizard' ) );

        // Autoptimize / caching plugin exclusions - prevents JS optimizers from
        // separating the inline claudeAI/claudeChat data objects from their script tags.
        add_filter( 'autoptimize_filter_js_exclude', function( $excluded ) {
            return $excluded . ', claude-ai-copywriter, admin.js, chat-assistant.js, copywriting-studio.js, site-intelligence.js, post-meta-box.js';
        } );
        add_filter( 'autoptimize_filter_css_exclude', function( $excluded ) {
            return $excluded . ', claude-ai-copywriter';
        } );
        // WP Rocket exclusion
        add_filter( 'rocket_exclude_js', function( $excluded ) {
            $excluded[] = '/wp-content/plugins/claude-ai-copywriter/assets/js/(.*)';
            return $excluded;
        } );
        // LiteSpeed Cache exclusion
        add_filter( 'litespeed_optimize_js_excludes', function( $excluded ) {
            $excluded[] = 'claude-ai-copywriter';
            return $excluded;
        } );

        // Trial notice dismissal
        add_action( 'wp_ajax_kc_dismiss_trial_notice', array( $this, 'ajax_dismiss_trial_notice' ) );

        // AJAX handlers for content generation
        add_action( 'wp_ajax_claude_generate_content', array( $this, 'ajax_generate_content' ) );
        add_action( 'wp_ajax_claude_test_api_key', array( $this, 'ajax_test_api_key' ) );
        add_action( 'wp_ajax_claude_save_api_key', array( $this, 'ajax_save_api_key' ) );
        add_action( 'wp_ajax_claude_save_calendar_push_secret', array( $this, 'ajax_save_calendar_push_secret' ) );
        add_action( 'wp_ajax_kineticcopy_save_provider_key',    array( $this, 'ajax_save_provider_key' ) );
        add_action( 'wp_ajax_kineticcopy_save_substack_email', array( $this, 'ajax_save_substack_email' ) );
        add_action( 'wp_ajax_kineticcopy_send_to_substack',    array( $this, 'ajax_send_to_substack' ) );

        // AJAX handlers for tone templates
        add_action( 'wp_ajax_claude_create_tone_template', array( $this, 'ajax_create_tone_template' ) );
        add_action( 'wp_ajax_claude_update_tone_template', array( $this, 'ajax_update_tone_template' ) );
        add_action( 'wp_ajax_claude_delete_tone_template', array( $this, 'ajax_delete_tone_template' ) );
        add_action( 'wp_ajax_claude_get_tone_templates', array( $this, 'ajax_get_tone_templates' ) );

        // AJAX handlers for personas
        add_action( 'wp_ajax_claude_create_persona', array( $this, 'ajax_create_persona' ) );
        add_action( 'wp_ajax_claude_update_persona', array( $this, 'ajax_update_persona' ) );
        add_action( 'wp_ajax_claude_delete_persona', array( $this, 'ajax_delete_persona' ) );
        add_action( 'wp_ajax_claude_get_personas', array( $this, 'ajax_get_personas' ) );

        // AJAX handlers for snippets
        add_action( 'wp_ajax_claude_save_snippet',   array( $this, 'ajax_save_snippet' ) );
        add_action( 'wp_ajax_claude_update_snippet', array( $this, 'ajax_update_snippet' ) );
        add_action( 'wp_ajax_claude_rate_snippet',   array( $this, 'ajax_rate_snippet' ) );
        add_action( 'wp_ajax_claude_delete_snippet', array( $this, 'ajax_delete_snippet' ) );
        add_action( 'wp_ajax_claude_get_snippets',   array( $this, 'ajax_get_snippets' ) );
        add_action( 'wp_ajax_claude_get_snippet',    array( $this, 'ajax_get_snippet' ) );
        // AJAX handlers for folder management
        add_action( 'wp_ajax_claude_get_folders',    array( $this, 'ajax_get_folders' ) );
        add_action( 'wp_ajax_claude_rename_folder',  array( $this, 'ajax_rename_folder' ) );
        add_action( 'wp_ajax_claude_delete_folder',  array( $this, 'ajax_delete_folder' ) );

        // AJAX handler for content analysis
        add_action( 'wp_ajax_claude_analyze_post', array( $this, 'ajax_analyze_post' ) );

        // AJAX handler for research
        add_action( 'wp_ajax_claude_search_web', array( $this, 'ajax_search_web' ) );

        // AJAX handler for saving draft post from Studio
        add_action( 'wp_ajax_claude_save_draft_post', array( $this, 'ajax_save_draft_post' ) );

        // AJAX handlers for KineticCopy draft-post features (v1.8.0)
        add_action( 'wp_ajax_kc_create_draft_from_snippet', array( $this, 'ajax_create_draft_from_snippet' ) );
        add_action( 'wp_ajax_claude_save_settings',         array( $this, 'ajax_save_settings' ) );

        // AJAX handlers for Content Calendar (v1.9.0)
        add_action( 'wp_ajax_kc_calendar_events',      array( $this, 'ajax_calendar_events' ) );
        add_action( 'wp_ajax_kc_calendar_reschedule',  array( $this, 'ajax_calendar_reschedule' ) );
        add_action( 'wp_ajax_kc_calendar_create_entry',array( $this, 'ajax_calendar_create_entry' ) );
        add_action( 'wp_ajax_kc_calendar_delete_entry',array( $this, 'ajax_calendar_delete_entry' ) );
        add_action( 'wp_ajax_kc_campaign_save',        array( $this, 'ajax_campaign_save' ) );
        add_action( 'wp_ajax_kc_campaign_delete',      array( $this, 'ajax_campaign_delete' ) );

        // Register Site Intelligence admin hooks
        Claude_AI_Copywriter_Site_Intelligence_Admin::register_ajax_hooks();
        add_action( 'admin_enqueue_scripts', array( 'Claude_AI_Copywriter_Site_Intelligence_Admin', 'enqueue_scripts' ) );
        add_action( 'admin_init', array( 'Claude_AI_Copywriter_Site_Intelligence_Admin', 'register_settings' ) );

        // Chat Assistant - only load on specific admin pages to avoid conflicts
        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_chat_assets' ) );
        add_action( 'admin_footer', array( $this, 'render_chat_window' ) );

        // Initialize Post Meta Box for tone analysis
        Claude_AI_Copywriter_Post_Meta_Box::init();
    }

    /**
     * Add kc-admin-page body class on all KineticCopy admin pages.
     * Used to scope bone-background CSS without affecting other plugins.
     */
    public function add_kc_body_class( $classes ) {
        $screen = get_current_screen();
        if ( $screen && (
            strpos( $screen->id, 'kineticcopy' ) !== false ||
            strpos( $screen->id, 'claude-ai-copywriter' ) !== false
        ) ) {
            $classes .= ' kc-admin-page';
        }
        return $classes;
    }

    /**
     * Add admin menu pages
     */
    public function add_admin_menu() {
        // Main menu page - Copywriting Studio
        add_menu_page(
            __( 'KineticCopy', 'claude-ai-copywriter' ),
            __( 'KineticCopy', 'claude-ai-copywriter' ),
            'edit_posts',
            'kineticcopy',
            array( new Claude_AI_Copywriter_Copywriting_Studio(), 'render' ),
            CLAUDE_AI_COPYWRITER_URL . 'assets/images/kc-mark-bone.png',
            30
        );

        // Content Library page
        add_submenu_page(
            'kineticcopy',
            __( 'Content Library', 'claude-ai-copywriter' ),
            __( 'Content Library', 'claude-ai-copywriter' ),
            'edit_posts',
            'kineticcopy-library',
            array( new Claude_AI_Copywriter_Library_Page(), 'render' )
        );

        // Content Calendar page (v1.9.0)
        add_submenu_page(
            'kineticcopy',
            __( 'Content Calendar', 'claude-ai-copywriter' ),
            __( 'Content Calendar', 'claude-ai-copywriter' ),
            'edit_posts',
            'kineticcopy-calendar',
            array( new Claude_AI_KineticCopy_Calendar_Page(), 'render' )
        );

        // Tone Templates page (registered for page access; hidden from nav below)
        add_submenu_page(
            'kineticcopy',
            __( 'Tone Templates', 'claude-ai-copywriter' ),
            __( 'Tone Templates', 'claude-ai-copywriter' ),
            'edit_posts',
            'kineticcopy-tone-templates',
            array( new Claude_AI_Copywriter_Tone_Templates_Page(), 'render' )
        );

        // Site Intelligence page (registered for page access; hidden from nav below)
        add_submenu_page(
            'kineticcopy',
            __( 'Site Intelligence', 'claude-ai-copywriter' ),
            __( 'Site Intelligence', 'claude-ai-copywriter' ),
            'manage_options',
            'kineticcopy-site-intelligence',
            array( 'Claude_AI_Copywriter_Site_Intelligence_Admin', 'render_page' )
        );

        // My Brand page — Brand Strategy + Site Intelligence + Personas in one place
        add_submenu_page(
            'kineticcopy',
            __( 'My Brand', 'claude-ai-copywriter' ),
            __( 'My Brand', 'claude-ai-copywriter' ),
            'manage_options',
            'kineticcopy-my-brand',
            array( new Claude_AI_KineticCopy_My_Brand_Page(), 'render' )
        );

        // Settings page
        add_submenu_page(
            'kineticcopy',
            __( 'Settings', 'claude-ai-copywriter' ),
            __( 'Settings', 'claude-ai-copywriter' ),
            'manage_options',
            'kineticcopy-settings',
            array( new Claude_AI_Copywriter_Settings_Page(), 'render' )
        );

        // Hide Tone Templates and Site Intelligence from the sidebar nav
        // (pages remain accessible by direct URL / internal links)
        remove_submenu_page( 'kineticcopy', 'kineticcopy-tone-templates' );
        remove_submenu_page( 'kineticcopy', 'kineticcopy-site-intelligence' );
    }

    /**
     * Enqueue admin assets (CSS and JS)
     */
    /**
     * Output global admin <head> CSS — loads on every WP admin page.
     * Keeps the sidebar menu icon correctly sized regardless of which page is active.
     */
    public function output_global_admin_head() {
        ?>
        <style id="kc-global-admin-head">
        #adminmenu li.toplevel_page_kineticcopy .wp-menu-image {
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            height: 34px !important;
            width: 36px !important;
        }
        #adminmenu li.toplevel_page_kineticcopy .wp-menu-image img {
            width: 20px !important;
            height: 20px !important;
            max-width: 20px !important;
            max-height: 20px !important;
            object-fit: contain !important;
            padding: 0 !important;
            margin: 0 !important;
            display: block !important;
        }
        </style>
        <?php
    }

    public function enqueue_admin_assets( $hook ) {
        // Enqueue post meta box assets on post/page edit screens
        $post_edit_screens = array( 'post.php', 'post-new.php' );
        if ( in_array( $hook, $post_edit_screens ) ) {
            wp_enqueue_script(
                'claude-ai-post-meta-box',
                CLAUDE_AI_COPYWRITER_URL . 'assets/js/post-meta-box.js',
                array( 'jquery' ),
                CLAUDE_AI_COPYWRITER_VERSION,
                true
            );

            wp_localize_script( 'claude-ai-post-meta-box', 'claudePostMetaBox', array(
                'ajax_url' => admin_url( 'admin-ajax.php' ),
                'nonce' => wp_create_nonce( 'claude_tone_analysis' ),
                'strings' => array(
                    'content_too_short' => __( 'Please add at least 100 characters of content before analyzing tone.', 'claude-ai-copywriter' ),
                    'error' => __( 'An error occurred. Please try again.', 'claude-ai-copywriter' ),
                    'no_tones' => __( 'No tones available to save.', 'claude-ai-copywriter' ),
                    'enter_template_name' => __( 'Enter a name for this tone template:', 'claude-ai-copywriter' ),
                    'tones' => __( 'Tones', 'claude-ai-copywriter' ),
                    'confidence' => __( 'Confidence: %d%%', 'claude-ai-copywriter' ),
                    'overall_confidence' => __( 'Overall confidence: %d%%', 'claude-ai-copywriter' ),
                ),
            ) );

            return; // Early return for post edit screens
        }

        // Only load on plugin pages
        if ( strpos( $hook, 'kineticcopy' ) === false ) {
            return;
        }

        // Enqueue Google Fonts — KineticCopy design system
        wp_enqueue_style(
            'kc-google-fonts',
            'https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500;600&display=swap',
            array(),
            null
        );

        // Enqueue CSS
        wp_enqueue_style(
            'claude-ai-admin',
            CLAUDE_AI_COPYWRITER_URL . 'assets/css/admin.css',
            array( 'kc-google-fonts' ),
            CLAUDE_AI_COPYWRITER_VERSION
        );

        // Enqueue JavaScript
        wp_enqueue_script(
            'claude-ai-admin',
            CLAUDE_AI_COPYWRITER_URL . 'assets/js/admin.js',
            array( 'jquery' ),
            CLAUDE_AI_COPYWRITER_VERSION,
            true
        );

        // Enqueue Copywriting Studio JS
        if ( $hook === 'toplevel_page_kineticcopy' ) {
            wp_enqueue_script(
                'claude-ai-studio',
                CLAUDE_AI_COPYWRITER_URL . 'assets/js/copywriting-studio.js',
                array( 'jquery', 'claude-ai-admin' ),
                CLAUDE_AI_COPYWRITER_VERSION,
                true
            );
        }

        // Enqueue Breakdance integration JS as a fallback for builders that don't
        // fire breakdance_register_scripts (class-breakdance-integration.php is the
        // primary path; this catches edge cases like older Breakdance versions).
        $settings = get_option( 'claude_ai_copywriter_settings', array() );
        if ( ! empty( $settings['enable_breakdance'] ) && isset( $_GET['breakdance'] ) ) {
            wp_enqueue_script(
                'claude-ai-breakdance',
                CLAUDE_AI_COPYWRITER_URL . 'assets/js/breakdance-integration.js',
                array( 'jquery' ),
                CLAUDE_AI_COPYWRITER_VERSION,
                true
            );
        }

        // Build AJAX data object
        $ajax_data = array(
            'ajax_url' => admin_url( 'admin-ajax.php' ),
            'nonce'    => wp_create_nonce( 'claude_ai_nonce' ),
            'settings' => array(
                'show_branded_personas_only' => ! empty( $settings['show_branded_personas_only'] ),
            ),
            'strings'  => array(
                'generating'          => __( 'Generating content...', 'claude-ai-copywriter' ),
                'error'               => __( 'An error occurred. Please try again.', 'claude-ai-copywriter' ),
                'success'             => __( 'Success!', 'claude-ai-copywriter' ),
                'confirm_delete'      => __( 'Are you sure you want to delete this item?', 'claude-ai-copywriter' ),
                'your_brand_personas' => __( 'Your Brand Personas', 'claude-ai-copywriter' ),
                'generic_personas'    => __( 'Generic Personas', 'claude-ai-copywriter' ),
            ),
        );

        // PRIMARY: wp_localize_script (standard WP)
        wp_localize_script( 'claude-ai-admin', 'claudeAI', $ajax_data );

        // AUTOPTIMIZE-PROOF BACKUP: wp_add_inline_script('before') bakes the data
        // directly INTO the combined JS file when Autoptimize is active, so it cannot
        // be separated from admin.js regardless of optimizer settings.
        wp_add_inline_script(
            'claude-ai-admin',
            'window.claudeAI = window.claudeAI || ' . wp_json_encode( $ajax_data ) . ';',
            'before'
        );

        // ─── Content Calendar page assets (v1.9.0) ───
        if ( strpos( $hook, 'kineticcopy-calendar' ) !== false ) {
            // FullCalendar v6 from CDN (MIT license, ~45KB gzipped)
            wp_enqueue_script(
                'fullcalendar',
                'https://cdn.jsdelivr.net/npm/fullcalendar@6.1.15/index.global.min.js',
                array(),
                '6.1.15',
                true
            );

            wp_enqueue_script(
                'kc-calendar',
                CLAUDE_AI_COPYWRITER_URL . 'assets/js/kc-calendar.js',
                array( 'fullcalendar', 'jquery' ),
                CLAUDE_AI_COPYWRITER_VERSION,
                true
            );

            wp_enqueue_style(
                'kc-calendar',
                CLAUDE_AI_COPYWRITER_URL . 'assets/css/kc-calendar.css',
                array(),
                CLAUDE_AI_COPYWRITER_VERSION
            );

            $cal_data = array(
                'ajax_url'       => admin_url( 'admin-ajax.php' ),
                'nonce'          => wp_create_nonce( 'claude_ai_nonce' ),
                'generate_nonce' => wp_create_nonce( 'kc_generate_nonce' ),
                'campaigns'      => array_values( get_option( 'kc_campaigns', array() ) ),
                'strings'        => array(
                    'all_campaigns'          => __( 'All Campaigns', 'claude-ai-copywriter' ),
                    'no_campaign'            => __( 'None', 'claude-ai-copywriter' ),
                    'no_campaigns_yet'       => __( 'No campaigns yet. Create one below.', 'claude-ai-copywriter' ),
                    'add_campaign'           => __( 'Add New Campaign', 'claude-ai-copywriter' ),
                    'create_entry'           => __( 'Create Entry', 'claude-ai-copywriter' ),
                    'entry_created'          => __( 'Entry created!', 'claude-ai-copywriter' ),
                    'rescheduled'            => __( 'Rescheduled!', 'claude-ai-copywriter' ),
                    'deleted'                => __( 'Entry deleted.', 'claude-ai-copywriter' ),
                    'generating'             => __( 'Generating content...', 'claude-ai-copywriter' ),
                    'generated'              => __( 'Content generated!', 'claude-ai-copywriter' ),
                    'words'                  => __( 'words', 'claude-ai-copywriter' ),
                    'generate'               => __( 'Generate Content', 'claude-ai-copywriter' ),
                    'regenerate'             => __( 'Regenerate Content', 'claude-ai-copywriter' ),
                    'campaign_saved'         => __( 'Campaign saved.', 'claude-ai-copywriter' ),
                    'campaign_deleted'       => __( 'Campaign deleted.', 'claude-ai-copywriter' ),
                    'confirm_delete'         => __( 'Delete this calendar entry? The draft post will be trashed.', 'claude-ai-copywriter' ),
                    'confirm_delete_campaign'=> __( 'Delete this campaign? Entries will keep their data but lose campaign association.', 'claude-ai-copywriter' ),
                    'all_posts'              => __( 'All Posts', 'claude-ai-copywriter' ),
                    'kc_only'               => __( 'KineticCopy Only', 'claude-ai-copywriter' ),
                    'open_in_editor'        => __( 'Open in Editor', 'claude-ai-copywriter' ),
                ),
            );

            wp_localize_script( 'kc-calendar', 'kcCalendar', $cal_data );
            // Autoptimize-proof backup
            wp_add_inline_script(
                'kc-calendar',
                'window.kcCalendar = window.kcCalendar || ' . wp_json_encode( $cal_data ) . ';',
                'before'
            );
        }
    }

    /**
     * Display admin notices
     */
    public function display_notices() {
        // Encryption notice on activation
        if ( get_transient( 'claude_ai_copywriter_encryption_notice' ) ) {
            echo '<div class="notice notice-info is-dismissible">';
            echo '<p>' . __( 'KineticCopy activated! Configure your API key in Settings to get started.', 'claude-ai-copywriter' ) . '</p>';
            echo '</div>';
            delete_transient( 'claude_ai_copywriter_encryption_notice' );
        }

        // Trial / free floor notices - never show if user has a valid license
        $cached_status = Claude_AI_Copywriter_License_Manager::get_cached_status();
        $has_valid_license = ! empty( $cached_status['valid'] );
        $is_owner          = ( class_exists( 'Claude_AI_Copywriter_Activator' ) && Claude_AI_Copywriter_Activator::is_owner_install() );

        if ( $has_valid_license || $is_owner ) {
            return;
        }

        $upgrade_url = esc_url( admin_url( 'admin.php?page=kineticcopy-settings&tab=upgrade' ) );

        if ( Claude_AI_Copywriter_License_Manager::is_trial_active() ) {
            // During trial: show once per day (session transient key includes the day)
            $dismiss_key = 'kc_trial_notice_dismissed_' . date( 'Y-m-d' );
            if ( get_transient( $dismiss_key ) ) {
                return;
            }
            $days_left = Claude_AI_Copywriter_License_Manager::get_trial_days_remaining();
            $days_text = sprintf(
                _n( '%d day', '%d days', $days_left, 'claude-ai-copywriter' ),
                $days_left
            );
            echo '<div class="notice notice-warning is-dismissible kc-trial-notice" data-dismiss-key="' . esc_attr( $dismiss_key ) . '">';
            echo '<p>';
            printf(
                /* translators: %1$s = days remaining, %2$s = upgrade URL */
                __( '<strong>KineticCopy:</strong> You\'re on day %1$s of your 7-day premium trial. <a href="%2$s">Upgrade now</a> to keep all features.', 'claude-ai-copywriter' ),
                esc_html( $days_text ),
                $upgrade_url
            );
            echo '</p>';
            echo '</div>';
            // Register JS to store dismissal in transient via AJAX
            add_action( 'admin_footer', array( $this, 'render_trial_notice_dismiss_script' ) );
        } elseif ( Claude_AI_Copywriter_License_Manager::trial_was_started() ) {
            // Trial expired and no license: show once per week
            $dismiss_key = 'kc_expired_notice_dismissed_' . date( 'Y-W' );
            if ( get_transient( $dismiss_key ) ) {
                return;
            }
            echo '<div class="notice notice-info is-dismissible kc-trial-notice" data-dismiss-key="' . esc_attr( $dismiss_key ) . '">';
            echo '<p>';
            printf(
                /* translators: %1$s = upgrade URL */
                __( '<strong>KineticCopy:</strong> Your premium trial has ended. You still have access to 10 content types and AEO/GEO schema. <a href="%1$s">Upgrade</a> to restore Brain, Calendar Sync, and all 43 formats.', 'claude-ai-copywriter' ),
                $upgrade_url
            );
            echo '</p>';
            echo '</div>';
            add_action( 'admin_footer', array( $this, 'render_trial_notice_dismiss_script' ) );
        }
    }

    /**
     * Output JS to handle trial notice dismissal - saves to transient via AJAX.
     */
    public function render_trial_notice_dismiss_script() {
        ?>
        <script>
        (function($){
            $('.kc-trial-notice').on('click', '.notice-dismiss', function(){
                var key = $(this).closest('.kc-trial-notice').data('dismiss-key');
                if (!key) return;
                $.post(ajaxurl, {
                    action: 'kc_dismiss_trial_notice',
                    nonce:  '<?php echo esc_js( wp_create_nonce( 'kc_dismiss_trial' ) ); ?>',
                    key:    key
                });
            });
        })(jQuery);
        </script>
        <?php
    }

    /**
     * Enqueue chat assistant assets - only on specific pages
     */
    public function enqueue_chat_assets( $hook ) {
        // Load on all WP admin pages for any logged-in editor/admin
        if ( ! current_user_can( 'edit_posts' ) ) {
            return;
        }
        // Avoid loading on the login screen or non-admin pages
        if ( ! is_admin() ) {
            return;
        }
        wp_enqueue_style(
            'claude-chat-assistant',
            CLAUDE_AI_COPYWRITER_URL . 'assets/css/chat-assistant.css',
            array(),
            CLAUDE_AI_COPYWRITER_VERSION
        );

        // Enqueue JS
        wp_enqueue_script(
            'claude-chat-assistant',
            CLAUDE_AI_COPYWRITER_URL . 'assets/js/chat-assistant.js',
            array( 'jquery' ),
            CLAUDE_AI_COPYWRITER_VERSION,
            true
        );

        // Localize script (standard WP)
        $chat_data = array(
            'ajax_url' => admin_url( 'admin-ajax.php' ),
            'nonce'    => wp_create_nonce( 'claude_chat_nonce' ),
            'site_url' => get_site_url(), // used by Brain greeting sessionStorage key
        );
        wp_localize_script( 'claude-chat-assistant', 'claudeChat', $chat_data );

        // Autoptimize-proof backup
        wp_add_inline_script(
            'claude-chat-assistant',
            'window.claudeChat = window.claudeChat || ' . wp_json_encode( $chat_data ) . ';',
            'before'
        );
    }

    /**
     * Render chat window - only on specific pages
     */
    public function render_chat_window() {
        // Render on all WP admin pages for editors/admins
        if ( ! current_user_can( 'edit_posts' ) ) {
            return;
        }

        // Brain is a premium feature - show upgrade nudge for free/trial-expired users
        if ( ! Claude_AI_Copywriter_License_Manager::is_premium() ) {
            $upgrade_url = esc_url( admin_url( 'admin.php?page=kineticcopy-settings&tab=upgrade' ) );
            echo '<div id="kc-brain-premium-nudge" class="kc-brain-locked-bubble" style="position:fixed;bottom:20px;right:20px;z-index:9999;background:#1e1e2e;color:#fff;border-radius:12px;padding:14px 18px;max-width:260px;box-shadow:0 4px 20px rgba(0,0,0,.3);font-size:13px;line-height:1.5;">';
            echo '<strong style="display:block;margin-bottom:6px;">&#128274; Brain AI Assistant</strong>';
            echo '<span style="opacity:.85;">' . esc_html__( 'The Brain is a premium feature.', 'claude-ai-copywriter' ) . '</span> ';
            echo '<a href="' . $upgrade_url . '" style="color:#ffee89;white-space:nowrap;">' . esc_html__( 'Upgrade KineticCopy', 'claude-ai-copywriter' ) . '</a>';
            echo '</div>';
            return;
        }

        // Include chat window template
        require_once CLAUDE_AI_COPYWRITER_PATH . 'admin/partials/chat-window.php';
    }

    /**
     * AJAX: Generate content with Claude
     */
    public function ajax_generate_content() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'claude-ai-copywriter' ) ) );
        }

        // Increase PHP execution time for content generation
        // This prevents timeouts for complex requests with research/fact-checking
        $current_time_limit = ini_get( 'max_execution_time' );
        if ( $current_time_limit > 0 && $current_time_limit < 240 ) {
            @set_time_limit( 240 ); // 4 minutes for PHP execution
        }

        // ── Security: rate limiting (50 requests per user per hour - anti-abuse) ──
        $user_id    = get_current_user_id();
        $rate_key   = "claude_gen_rate_{$user_id}";
        $rate_count = (int) get_transient( $rate_key );
        if ( $rate_count >= 50 ) {
            wp_send_json_error( array(
                'message' => __( "You've hit the rate limit (50 requests/hour). Please wait a few minutes and try again.", 'claude-ai-copywriter' ),
            ) );
        }
        if ( $rate_count === 0 ) {
            set_transient( $rate_key, 1, HOUR_IN_SECONDS );
        } else {
            set_transient( $rate_key, $rate_count + 1, HOUR_IN_SECONDS );
        }

        $prompt         = sanitize_textarea_field( $_POST['prompt']         ?? '' );
        $source_content = sanitize_textarea_field( wp_unslash( $_POST['source_content'] ?? '' ) );
        $tone_template_id = intval( $_POST['tone_template_id'] ?? 0 );
        $persona_id     = intval( $_POST['persona_id']     ?? 0 );
        $content_type   = sanitize_text_field( $_POST['content_type']   ?? 'general' );
        $constraints    = isset( $_POST['constraints'] ) ? json_decode( stripslashes( $_POST['constraints'] ), true ) : array();
        $audience_target = sanitize_text_field( $_POST['audience_target'] ?? 'both' );
        if ( ! in_array( $audience_target, array( 'primary', 'secondary', 'both' ), true ) ) {
            $audience_target = 'both';
        }

        if ( empty( $prompt ) ) {
            wp_send_json_error( array( 'message' => __( 'Prompt is required.', 'claude-ai-copywriter' ) ) );
        }

        // ── Free floor: block premium content types for non-premium users ─────
        if ( ! Claude_AI_Copywriter_License_Manager::is_premium() ) {
            $free_types = Claude_AI_Copywriter_Snippet_Manager::get_free_snippet_types();
            if ( ! in_array( $content_type, $free_types, true ) ) {
                wp_send_json_error( array(
                    'message' => __( 'This content type requires a KineticCopy license. Upgrade to unlock all 43 formats.', 'claude-ai-copywriter' ),
                    'upgrade_url' => admin_url( 'admin.php?page=kineticcopy-settings&tab=upgrade' ),
                ) );
            }
        }

        // ── Security: prompt length cap (5000 characters) ────────────────────
        if ( mb_strlen( $prompt ) > 5000 ) {
            wp_send_json_error( array(
                'message' => sprintf(
                    __( 'Your prompt is too long (%d characters). Please keep it under 5,000 characters.', 'claude-ai-copywriter' ),
                    mb_strlen( $prompt )
                ),
            ) );
        }

        // ── Security: basic prompt injection detection ────────────────────────
        if ( $this->contains_prompt_injection( $prompt ) ) {
            wp_send_json_error( array(
                'message' => __( "That prompt couldn't be processed. Please rephrase it as a straightforward content request.", 'claude-ai-copywriter' ),
            ) );
        }

        // Get tones - NEW v1.2.0: Support both template and custom selection
        $tones = array();

        // First, check for directly selected tones (v1.2.0 multi-tone feature)
        if ( isset( $_POST['selected_tones'] ) && is_array( $_POST['selected_tones'] ) ) {
            $tones = array_map( 'sanitize_text_field', $_POST['selected_tones'] );
        }
        // Fallback to tone template if no direct selection
        elseif ( $tone_template_id > 0 ) {
            $template = Claude_AI_Copywriter_Tone_Manager::get_template( $tone_template_id );
            if ( $template && isset( $template->tones ) ) {
                $tones = $template->tones;
            }
        }
        // Final fallback: site default tone template from Settings
        if ( empty( $tones ) ) {
            $settings_for_tone    = get_option( 'claude_ai_copywriter_settings', array() );
            $default_template_id  = intval( $settings_for_tone['default_tone_template_id'] ?? 0 );
            if ( $default_template_id > 0 ) {
                $default_tmpl = Claude_AI_Copywriter_Tone_Manager::get_template( $default_template_id );
                if ( $default_tmpl && isset( $default_tmpl->tones ) ) {
                    $tones = $default_tmpl->tones;
                }
            }
        }

        // Generate content
        $claude_api = new Claude_AI_Copywriter_Claude_API();

        $generate_options = array(
            'tones'           => $tones,
            'persona'         => $persona_id,
            'content_type'    => $content_type,
            'constraints'     => $constraints,
            'source_content'  => $source_content,
            'audience_target' => $audience_target,
        );

        // Allow per-generation model override (user picks Opus for complex strategy work)
        $model_override = sanitize_text_field( $_POST['model'] ?? '' );
        $allowed_models = array_keys( Claude_AI_Copywriter_Claude_API::get_available_models() );
        if ( ! empty( $model_override ) && in_array( $model_override, $allowed_models, true ) ) {
            $generate_options['model']      = $model_override;
            $generate_options['max_tokens'] = 8192; // Full budget when user explicitly picks a model
        }

        $response = $claude_api->generate_content( $prompt, $generate_options );

        if ( is_wp_error( $response ) ) {
            // Provide more helpful error message for timeout errors
            $error_message = $response->get_error_message();
            if ( strpos( $error_message, 'timeout' ) !== false || strpos( $error_message, 'timed out' ) !== false ) {
                $error_message .= ' ' . __( 'Your content request is taking longer than expected. Try simplifying your prompt or disabling research/fact-checking.', 'claude-ai-copywriter' );
            }
            wp_send_json_error( array( 'message' => $error_message ) );
        }

        // ── Security: strip any <script> tags from AI response before returning ──
        if ( ! empty( $response['content'] ) ) {
            $response['content'] = wp_kses( $response['content'], array(
                'h1' => array(), 'h2' => array(), 'h3' => array(), 'h4' => array(), 'h5' => array(), 'h6' => array(),
                'p' => array(), 'br' => array(), 'hr' => array(),
                'strong' => array(), 'em' => array(), 'b' => array(), 'i' => array(), 'u' => array(), 's' => array(),
                'ul' => array(), 'ol' => array(), 'li' => array(),
                'blockquote' => array(), 'pre' => array(), 'code' => array(),
                'a' => array( 'href' => array(), 'title' => array(), 'target' => array() ),
                'table' => array(), 'thead' => array(), 'tbody' => array(), 'tr' => array(), 'th' => array(), 'td' => array(),
            ) );
        }

        wp_send_json_success( $response );
    }

    /**
     * AJAX: Dismiss trial/expired notice for the rest of the day or week.
     * Stores a short-lived transient so the notice doesn't re-appear until the next period.
     */
    public function ajax_dismiss_trial_notice() {
        check_ajax_referer( 'kc_dismiss_trial', 'nonce' );

        $key = sanitize_key( $_POST['key'] ?? '' );
        if ( empty( $key ) || strpos( $key, 'kc_' ) !== 0 ) {
            wp_send_json_error();
        }

        // Store for 25 hours (ensures the notice stays hidden for the rest of the current day)
        set_transient( $key, '1', 25 * HOUR_IN_SECONDS );
        wp_send_json_success();
    }

    /**
     * AJAX: Test API key
     */
    public function ajax_test_api_key() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'claude-ai-copywriter' ) ) );
        }

        $api_key = sanitize_text_field( $_POST['api_key'] ?? '' );

        if ( empty( $api_key ) ) {
            wp_send_json_error( array( 'message' => __( 'API key is required.', 'claude-ai-copywriter' ) ) );
        }

        // Note: constructor takes user_id (not api_key). Pass key directly to test_api_key().
        $claude_api = new Claude_AI_Copywriter_Claude_API();
        $result = $claude_api->test_api_key( $api_key );

        if ( is_wp_error( $result ) ) {
            wp_send_json_error( array( 'message' => $result->get_error_message() ) );
        }

        // test_api_key() returns array('valid' => bool, 'message' => string) - check the flag.
        if ( empty( $result['valid'] ) ) {
            wp_send_json_error( array( 'message' => $result['message'] ?? __( 'API key test failed. Check your key and try again.', 'claude-ai-copywriter' ) ) );
        }

        wp_send_json_success( array( 'message' => $result['message'] ?? __( 'API key is valid!', 'claude-ai-copywriter' ) ) );
    }

    /**
     * AJAX: Save API key
     */
    public function ajax_save_api_key() {
        // Add debug logging
        error_log('[Claude AI] ajax_save_api_key called');
        
        // Verify nonce
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            error_log('[Claude AI] Permission denied');
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'claude-ai-copywriter' ) ) );
        }

        $api_key = sanitize_text_field( $_POST['api_key'] ?? '' );
        $key_name = sanitize_text_field( $_POST['key_name'] ?? 'Default' );

        error_log('[Claude AI] API key length: ' . strlen($api_key) . ', key_name: ' . $key_name);

        if ( empty( $api_key ) ) {
            wp_send_json_error( array( 'message' => __( 'API key is required.', 'claude-ai-copywriter' ) ) );
        }

        // Try to use the database method first
        if ( class_exists('Claude_AI_Copywriter_Claude_API') ) {
            $result = Claude_AI_Copywriter_Claude_API::save_api_key(
                $api_key,
                get_current_user_id(),
                $key_name
            );

            if ( is_wp_error( $result ) ) {
                error_log('[Claude AI] DB save failed: ' . $result->get_error_message());
                wp_send_json_error( array( 'message' => $result->get_error_message() ) );
            }
            
            error_log('[Claude AI] API key saved successfully via DB');
            wp_send_json_success( array( 'message' => __( 'API key saved successfully!', 'claude-ai-copywriter' ) ) );
        }
        
        // Fallback: Save directly to user meta if DB method not available
        update_user_meta( get_current_user_id(), 'claude_api_key', $api_key );
        error_log('[Claude AI] API key saved via user meta fallback');
        wp_send_json_success( array( 'message' => __( 'API key saved successfully!', 'claude-ai-copywriter' ) ) );
    }

    /**
     * AJAX: Save alternate provider key (KiloCode or OpenRouter)
     */
    public function ajax_save_provider_key() {
        check_ajax_referer( 'claude_ai_settings_save', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( __( 'Permission denied.', 'claude-ai-copywriter' ) );
        }

        $provider = sanitize_text_field( $_POST['provider'] ?? '' );
        $api_key  = sanitize_text_field( $_POST['api_key'] ?? '' );

        if ( ! in_array( $provider, array( 'kilocode', 'openrouter' ), true ) ) {
            wp_send_json_error( __( 'Invalid provider.', 'claude-ai-copywriter' ) );
        }

        if ( empty( $api_key ) ) {
            wp_send_json_error( __( 'API key is required.', 'claude-ai-copywriter' ) );
        }

        // Encrypt the key before storage
        $encrypted_data = Claude_AI_Copywriter_Encryption::encrypt( $api_key );
        if ( is_wp_error( $encrypted_data ) ) {
            wp_send_json_error( $encrypted_data->get_error_message() );
        }

        $user_id = get_current_user_id();
        update_user_meta( $user_id, 'kineticcopy_' . $provider . '_key', $encrypted_data['encrypted'] );
        update_user_meta( $user_id, 'kineticcopy_' . $provider . '_iv', $encrypted_data['iv'] );

        wp_send_json_success( array( 'message' => sprintf( __( '%s key saved.', 'claude-ai-copywriter' ), ucfirst( $provider ) ) ) );
    }

    /**
     * AJAX: Save Substack publication email
     */
    public function ajax_save_substack_email() {
        check_ajax_referer( 'claude_ai_settings_save', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( __( 'Permission denied.', 'claude-ai-copywriter' ) );
        }

        $email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) );

        if ( ! is_email( $email ) ) {
            wp_send_json_error( __( 'Please enter a valid email address.', 'claude-ai-copywriter' ) );
        }

        update_option( 'kineticcopy_substack_email', $email );
        wp_send_json_success( array( 'message' => __( 'Substack email saved.', 'claude-ai-copywriter' ) ) );
    }

    /**
     * AJAX: Send a snippet to Substack via email-to-post
     */
    public function ajax_send_to_substack() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( __( 'Permission denied.', 'claude-ai-copywriter' ) );
        }

        $to = get_option( 'kineticcopy_substack_email', '' );
        if ( ! is_email( $to ) ) {
            wp_send_json_error( __( 'No Substack email configured. Go to Settings &rarr; API Keys &amp; Integrations &rarr; Substack.', 'claude-ai-copywriter' ) );
        }

        $snippet_id = intval( $_POST['snippet_id'] ?? 0 );
        if ( ! $snippet_id ) {
            wp_send_json_error( __( 'Invalid snippet.', 'claude-ai-copywriter' ) );
        }

        global $wpdb;
        $table   = $wpdb->prefix . 'claude_snippets';
        $snippet = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $snippet_id ) );

        if ( ! $snippet ) {
            wp_send_json_error( __( 'Snippet not found.', 'claude-ai-copywriter' ) );
        }

        // Prefer generated content (the written post) over the prompt
        $body = ! empty( $snippet->generated_content ) ? $snippet->generated_content : $snippet->prompt_text;
        if ( empty( $body ) ) {
            wp_send_json_error( __( 'Snippet has no content to send.', 'claude-ai-copywriter' ) );
        }

        // Derive subject from tags - first non-generic tag becomes the post title
        $subject = __( 'Substack Post', 'claude-ai-copywriter' );
        if ( ! empty( $snippet->tags ) ) {
            $skip = array( 'kineticlaunch', 'kineticcopy', 'substack', 'substack_post' );
            foreach ( array_filter( array_map( 'trim', explode( ',', $snippet->tags ) ) ) as $tag ) {
                if ( ! in_array( strtolower( $tag ), $skip, true ) ) {
                    $subject = $tag;
                    break;
                }
            }
        }

        // Wrap plain text in paragraphs so Substack preserves line breaks
        if ( strip_tags( $body ) === $body ) {
            $body = '<p>' . nl2br( esc_html( $body ) ) . '</p>';
        }

        $sent = wp_mail( $to, $subject, $body, array( 'Content-Type: text/html; charset=UTF-8' ) );

        if ( $sent ) {
            wp_send_json_success( array( 'message' => __( 'Sent to Substack - check your drafts at substack.com.', 'claude-ai-copywriter' ) ) );
        } else {
            wp_send_json_error( __( 'Email failed to send. Check your WordPress mail configuration (WP Mail SMTP recommended).', 'claude-ai-copywriter' ) );
        }
    }

    /**
     * AJAX: Create tone template
     */
    public function ajax_create_tone_template() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'claude-ai-copywriter' ) ) );
        }

        $data = array(
            'template_name' => sanitize_text_field( $_POST['template_name'] ?? '' ),
            'tones' => isset( $_POST['tones'] ) ? array_map( 'sanitize_text_field', $_POST['tones'] ) : array(),
            'description' => sanitize_textarea_field( $_POST['description'] ?? '' ),
        );

        $result = Claude_AI_Copywriter_Tone_Manager::create_template( $data );

        if ( is_wp_error( $result ) ) {
            wp_send_json_error( array( 'message' => $result->get_error_message() ) );
        }

        wp_send_json_success( array(
            'message' => __( 'Tone template created!', 'claude-ai-copywriter' ),
            'template_id' => $result,
        ) );
    }

    /**
     * AJAX: Update tone template
     */
    public function ajax_update_tone_template() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        $template_id = intval( $_POST['template_id'] ?? 0 );

        $data = array(
            'template_name' => sanitize_text_field( $_POST['template_name'] ?? '' ),
            'tones' => isset( $_POST['tones'] ) ? array_map( 'sanitize_text_field', $_POST['tones'] ) : array(),
            'description' => sanitize_textarea_field( $_POST['description'] ?? '' ),
        );

        $result = Claude_AI_Copywriter_Tone_Manager::update_template( $template_id, $data );

        if ( is_wp_error( $result ) ) {
            wp_send_json_error( array( 'message' => $result->get_error_message() ) );
        }

        wp_send_json_success( array( 'message' => __( 'Tone template updated!', 'claude-ai-copywriter' ) ) );
    }

    /**
     * AJAX: Delete tone template
     */
    public function ajax_delete_tone_template() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        $template_id = intval( $_POST['template_id'] ?? 0 );

        $result = Claude_AI_Copywriter_Tone_Manager::delete_template( $template_id );

        if ( is_wp_error( $result ) ) {
            wp_send_json_error( array( 'message' => $result->get_error_message() ) );
        }

        wp_send_json_success( array( 'message' => __( 'Tone template deleted!', 'claude-ai-copywriter' ) ) );
    }

    /**
     * AJAX: Get tone templates
     */
    public function ajax_get_tone_templates() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        $templates = Claude_AI_Copywriter_Tone_Manager::get_user_templates();

        wp_send_json_success( array( 'templates' => $templates ) );
    }

    /**
     * AJAX: Create persona
     */
    public function ajax_create_persona() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'claude-ai-copywriter' ) ) );
        }

        $data = array(
            'persona_name' => sanitize_text_field( $_POST['persona_name'] ?? '' ),
            'demographics' => isset( $_POST['demographics'] ) ? json_decode( stripslashes( $_POST['demographics'] ), true ) : array(),
            'psychographics' => isset( $_POST['psychographics'] ) ? json_decode( stripslashes( $_POST['psychographics'] ), true ) : array(),
            'communication_style' => sanitize_textarea_field( $_POST['communication_style'] ?? '' ),
        );

        $result = Claude_AI_Copywriter_Persona_Manager::create_persona( $data );

        if ( is_wp_error( $result ) ) {
            wp_send_json_error( array( 'message' => $result->get_error_message() ) );
        }

        wp_send_json_success( array(
            'message' => __( 'Persona created!', 'claude-ai-copywriter' ),
            'persona_id' => $result,
        ) );
    }

    /**
     * AJAX: Update persona
     */
    public function ajax_update_persona() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        $persona_id = intval( $_POST['persona_id'] ?? 0 );

        $data = array(
            'persona_name' => sanitize_text_field( $_POST['persona_name'] ?? '' ),
            'demographics' => isset( $_POST['demographics'] ) ? json_decode( stripslashes( $_POST['demographics'] ), true ) : array(),
            'psychographics' => isset( $_POST['psychographics'] ) ? json_decode( stripslashes( $_POST['psychographics'] ), true ) : array(),
            'communication_style' => sanitize_textarea_field( $_POST['communication_style'] ?? '' ),
        );

        $result = Claude_AI_Copywriter_Persona_Manager::update_persona( $persona_id, $data );

        if ( is_wp_error( $result ) ) {
            wp_send_json_error( array( 'message' => $result->get_error_message() ) );
        }

        wp_send_json_success( array( 'message' => __( 'Persona updated!', 'claude-ai-copywriter' ) ) );
    }

    /**
     * AJAX: Delete persona
     */
    public function ajax_delete_persona() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        $persona_id = intval( $_POST['persona_id'] ?? 0 );

        $result = Claude_AI_Copywriter_Persona_Manager::delete_persona( $persona_id );

        if ( is_wp_error( $result ) ) {
            wp_send_json_error( array( 'message' => $result->get_error_message() ) );
        }

        wp_send_json_success( array( 'message' => __( 'Persona deleted!', 'claude-ai-copywriter' ) ) );
    }

    /**
     * AJAX: Get personas
     */
    public function ajax_get_personas() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        $personas = Claude_AI_Copywriter_Persona_Manager::get_user_personas();

        wp_send_json_success( array( 'personas' => $personas ) );
    }

    /**
     * AJAX: Save snippet
     */
    public function ajax_save_snippet() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'claude-ai-copywriter' ) ) );
        }

        // Support both legacy flat format and new nested snippet{} format from studio
        $snippet_post = isset( $_POST['snippet'] ) && is_array( $_POST['snippet'] )
            ? $_POST['snippet']
            : $_POST;

        // Build context_data: generation settings + example output
        $context_data = array();
        if ( isset( $snippet_post['content_type'] ) ) {
            $context_data['content_type'] = sanitize_text_field( $snippet_post['content_type'] );
        }
        if ( isset( $snippet_post['tones'] ) && is_array( $snippet_post['tones'] ) ) {
            $context_data['tones'] = array_map( 'sanitize_text_field', $snippet_post['tones'] );
        }
        if ( isset( $snippet_post['persona_id'] ) ) {
            $context_data['persona_id'] = intval( $snippet_post['persona_id'] );
        }
        if ( isset( $snippet_post['model_used'] ) ) {
            $context_data['model'] = sanitize_text_field( $snippet_post['model_used'] );
        }
        // generated_content is now its own DB column - keep in context_data for backward compat too
        $generated_content = '';
        if ( isset( $snippet_post['generated_content'] ) ) {
            $generated_content = wp_kses_post( wp_unslash( $snippet_post['generated_content'] ) );
        } elseif ( isset( $snippet_post['example_output'] ) ) {
            $generated_content = wp_kses_post( wp_unslash( $snippet_post['example_output'] ) );
        }
        if ( $generated_content ) {
            $context_data['example_output'] = $generated_content; // backward compat read path
        }

        $data = array(
            'snippet_type'      => sanitize_text_field( $snippet_post['snippet_type'] ?? '' ),
            'prompt_text'       => sanitize_textarea_field( wp_unslash( $snippet_post['prompt_text'] ?? $snippet_post['content'] ?? '' ) ), // content = legacy fallback
            'generated_content' => $generated_content,
            'tone_template_id'  => intval( $snippet_post['tone_template_id'] ?? 0 ),
            'persona_id'        => intval( $snippet_post['persona_id'] ?? 0 ),
            'tags'              => sanitize_text_field( $snippet_post['tags'] ?? '' ),
            'folder'            => sanitize_text_field( $snippet_post['folder'] ?? 'Uncategorized' ),
            'context_data'      => ! empty( $context_data ) ? $context_data : null,
        );

        $result = Claude_AI_Copywriter_Snippet_Manager::save_snippet( $data );

        if ( is_wp_error( $result ) ) {
            wp_send_json_error( array( 'message' => $result->get_error_message() ) );
        }

        wp_send_json_success( array(
            'message' => __( 'Snippet saved!', 'claude-ai-copywriter' ),
            'snippet_id' => $result,
        ) );
    }

    /**
     * AJAX: Rate snippet
     */
    public function ajax_rate_snippet() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        $snippet_id = intval( $_POST['snippet_id'] ?? 0 );
        $rating = intval( $_POST['rating'] ?? 0 );

        $result = Claude_AI_Copywriter_Snippet_Manager::rate_snippet( $snippet_id, $rating );

        if ( is_wp_error( $result ) ) {
            wp_send_json_error( array( 'message' => $result->get_error_message() ) );
        }

        wp_send_json_success( array( 'message' => __( 'Rating saved!', 'claude-ai-copywriter' ) ) );
    }

    /**
     * AJAX: Delete snippet
     */
    public function ajax_delete_snippet() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        $snippet_id = intval( $_POST['snippet_id'] ?? 0 );

        $result = Claude_AI_Copywriter_Snippet_Manager::delete_snippet( $snippet_id );

        if ( is_wp_error( $result ) ) {
            wp_send_json_error( array( 'message' => $result->get_error_message() ) );
        }

        wp_send_json_success( array( 'message' => __( 'Snippet deleted!', 'claude-ai-copywriter' ) ) );
    }

    /**
     * AJAX: Get snippets (supports flat params and nested filters:{} from library)
     */
    public function ajax_get_snippets() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        // Support both flat POST params (studio) and nested filters array (library)
        $filters = isset( $_POST['filters'] ) && is_array( $_POST['filters'] )
            ? $_POST['filters']
            : $_POST;

        $rating = null;
        if ( isset( $filters['rating'] ) && $filters['rating'] !== '' && $filters['rating'] !== null ) {
            $rating = intval( $filters['rating'] );
        }

        $args = array(
            'snippet_type' => sanitize_text_field( $filters['snippet_type'] ?? '' ),
            'folder'       => sanitize_text_field( $filters['folder'] ?? '' ),
            'search'       => sanitize_text_field( $filters['search'] ?? '' ),
            'limit'        => intval( $filters['limit'] ?? 50 ),
            'offset'       => intval( $filters['offset'] ?? 0 ),
            'orderby'      => sanitize_key( $filters['orderby'] ?? 'created_at' ),
            'order'        => strtoupper( sanitize_text_field( $filters['order'] ?? 'DESC' ) ) === 'ASC' ? 'ASC' : 'DESC',
            'rating'       => $rating,
        );

        $snippets = Claude_AI_Copywriter_Snippet_Manager::get_snippets( $args );

        // Count total (without pagination) for library
        $count_args = $args;
        $count_args['limit']  = 9999;
        $count_args['offset'] = 0;
        $total = count( Claude_AI_Copywriter_Snippet_Manager::get_snippets( $count_args ) );

        wp_send_json_success( array( 'snippets' => $snippets, 'total' => $total ) );
    }

    /**
     * AJAX: Get single snippet by ID (used by library preview/run-again)
     */
    public function ajax_get_snippet() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'claude-ai-copywriter' ) ) );
        }

        $snippet_id = intval( $_POST['snippet_id'] ?? 0 );
        $snippet    = Claude_AI_Copywriter_Snippet_Manager::get_snippet( $snippet_id );

        if ( ! $snippet ) {
            wp_send_json_error( array( 'message' => __( 'Snippet not found.', 'claude-ai-copywriter' ) ) );
        }

        // Ownership check
        if ( (int) $snippet->user_id !== get_current_user_id() && ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'claude-ai-copywriter' ) ) );
        }

        wp_send_json_success( array( 'snippet' => $snippet ) );
    }

    /**
     * AJAX: Update an existing snippet (edit modal)
     */
    public function ajax_update_snippet() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'claude-ai-copywriter' ) ) );
        }

        $snippet_id = intval( $_POST['snippet_id'] ?? 0 );
        if ( ! $snippet_id ) {
            wp_send_json_error( array( 'message' => __( 'Invalid snippet ID.', 'claude-ai-copywriter' ) ) );
        }

        $data = array();
        if ( isset( $_POST['prompt_text'] ) ) {
            $data['prompt_text'] = sanitize_textarea_field( wp_unslash( $_POST['prompt_text'] ) );
        }
        if ( isset( $_POST['snippet_type'] ) ) {
            $data['snippet_type'] = sanitize_text_field( $_POST['snippet_type'] );
        }
        if ( isset( $_POST['folder'] ) ) {
            $data['folder'] = sanitize_text_field( $_POST['folder'] );
        }
        if ( isset( $_POST['tags'] ) ) {
            $data['tags'] = sanitize_text_field( $_POST['tags'] );
        }
        if ( isset( $_POST['rating'] ) ) {
            $data['rating'] = absint( $_POST['rating'] );
        }

        $result = Claude_AI_Copywriter_Snippet_Manager::update_snippet( $snippet_id, $data );

        if ( is_wp_error( $result ) ) {
            wp_send_json_error( array( 'message' => $result->get_error_message() ) );
        }

        wp_send_json_success( array( 'message' => __( 'Snippet updated.', 'claude-ai-copywriter' ) ) );
    }

    /**
     * AJAX: Get all folders for current user (with snippet counts)
     */
    public function ajax_get_folders() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'claude-ai-copywriter' ) ) );
        }

        global $wpdb;
        $table = $wpdb->prefix . 'claude_snippets';
        $user_id = get_current_user_id();

        $rows = $wpdb->get_results( $wpdb->prepare(
            "SELECT folder, COUNT(*) as count FROM {$table} WHERE user_id = %d GROUP BY folder ORDER BY folder ASC",
            $user_id
        ) );

        wp_send_json_success( array( 'folders' => $rows ) );
    }

    /**
     * AJAX: Rename a folder (bulk-updates all snippets in that folder)
     */
    public function ajax_rename_folder() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'claude-ai-copywriter' ) ) );
        }

        $old_name = sanitize_text_field( wp_unslash( $_POST['old_name'] ?? '' ) );
        $new_name = sanitize_text_field( wp_unslash( $_POST['new_name'] ?? '' ) );

        if ( ! $old_name || ! $new_name ) {
            wp_send_json_error( array( 'message' => __( 'Folder name is required.', 'claude-ai-copywriter' ) ) );
        }

        global $wpdb;
        $table = $wpdb->prefix . 'claude_snippets';
        $user_id = get_current_user_id();

        $result = $wpdb->update(
            $table,
            array( 'folder' => $new_name ),
            array( 'folder' => $old_name, 'user_id' => $user_id ),
            array( '%s' ),
            array( '%s', '%d' )
        );

        if ( false === $result ) {
            wp_send_json_error( array( 'message' => __( 'Failed to rename folder.', 'claude-ai-copywriter' ) ) );
        }

        wp_send_json_success( array( 'message' => sprintf( __( 'Folder renamed to "%s".', 'claude-ai-copywriter' ), $new_name ), 'count' => $result ) );
    }

    /**
     * AJAX: Delete a folder (moves all snippets to Uncategorized)
     */
    public function ajax_delete_folder() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'claude-ai-copywriter' ) ) );
        }

        $folder_name = sanitize_text_field( wp_unslash( $_POST['folder_name'] ?? '' ) );

        if ( ! $folder_name || 'Uncategorized' === $folder_name ) {
            wp_send_json_error( array( 'message' => __( 'Cannot delete the Uncategorized folder.', 'claude-ai-copywriter' ) ) );
        }

        global $wpdb;
        $table = $wpdb->prefix . 'claude_snippets';
        $user_id = get_current_user_id();

        $result = $wpdb->update(
            $table,
            array( 'folder' => 'Uncategorized' ),
            array( 'folder' => $folder_name, 'user_id' => $user_id ),
            array( '%s' ),
            array( '%s', '%d' )
        );

        if ( false === $result ) {
            wp_send_json_error( array( 'message' => __( 'Failed to delete folder.', 'claude-ai-copywriter' ) ) );
        }

        wp_send_json_success( array( 'message' => __( 'Folder deleted. Snippets moved to Uncategorized.', 'claude-ai-copywriter' ), 'count' => $result ) );
    }

    /**
     * AJAX: Analyze post
     */
    public function ajax_analyze_post() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        $post_id = intval( $_POST['post_id'] ?? 0 );
        $force_refresh = isset( $_POST['force_refresh'] ) && $_POST['force_refresh'] === 'true';

        $analysis = Claude_AI_Copywriter_Content_Analyzer::analyze_post( $post_id, $force_refresh );

        if ( is_wp_error( $analysis ) ) {
            wp_send_json_error( array( 'message' => $analysis->get_error_message() ) );
        }

        wp_send_json_success( array( 'analysis' => $analysis ) );
    }

    /**
     * AJAX: Search web
     */
    public function ajax_search_web() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'claude-ai-copywriter' ) ) );
        }

        $query = sanitize_text_field( $_POST['query'] ?? '' );
        $num_results = intval( $_POST['num_results'] ?? 3 );

        $results = Claude_AI_Copywriter_Research_Engine::search_web( $query, $num_results );

        if ( is_wp_error( $results ) ) {
            wp_send_json_error( array( 'message' => $results->get_error_message() ) );
        }

        wp_send_json_success( array( 'results' => $results ) );
    }

    /**
     * AJAX: Save generated content as a WordPress draft post
     */
    public function ajax_save_draft_post() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'claude-ai-copywriter' ) ) );
        }

        $title        = sanitize_text_field( $_POST['title'] ?? '' );
        $content      = wp_kses_post( wp_unslash( $_POST['content'] ?? '' ) );
        $content_type = sanitize_key( $_POST['content_type'] ?? '' );

        if ( empty( $content ) ) {
            wp_send_json_error( array( 'message' => __( 'No content provided.', 'claude-ai-copywriter' ) ) );
        }

        if ( empty( $title ) ) {
            $title = __( 'KineticCopy Draft', 'claude-ai-copywriter' );
        }

        $post_id = wp_insert_post( array(
            'post_title'   => $title,
            'post_content' => $content,
            'post_status'  => 'draft',
            'post_author'  => get_current_user_id(),
        ) );

        if ( is_wp_error( $post_id ) ) {
            wp_send_json_error( array( 'message' => $post_id->get_error_message() ) );
        }

        // 2.7 - store schema markup in post meta for wp_head auto-inject
        if ( 'schema_markup' === $content_type ) {
            $raw_schema = wp_strip_all_tags( wp_unslash( $_POST['content'] ?? '' ) );
            // Ensure wrapped in <script> tags
            if ( strpos( trim( $raw_schema ), '<script' ) === false ) {
                $raw_schema = '<script type="application/ld+json">' . "\n" . trim( $raw_schema ) . "\n" . '</script>';
            }
            update_post_meta( $post_id, '_claude_schema_markup', $raw_schema );
        }

        wp_send_json_success( array(
            'post_id'  => $post_id,
            'edit_url' => get_edit_post_link( $post_id, 'raw' ),
        ) );
    }

    /**
     * AJAX: Save calendar push webhook secret
     * Called from Brand Strategy settings tab
     */
    public function ajax_save_calendar_push_secret() {
        check_ajax_referer( 'claude_ai_settings_save', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( 'Unauthorized' );
        }

        $secret = sanitize_text_field( wp_unslash( $_POST['secret'] ?? '' ) );

        if ( empty( $secret ) ) {
            wp_send_json_error( 'Secret cannot be empty.' );
        }

        update_option( 'kineticcopy_calendar_push_secret', $secret );

        wp_send_json_success( [
            'message' => 'Calendar push secret saved.',
            'secret_set' => true,
        ] );
    }

    /**
     * AJAX: Create a WP draft post from a kineticlaunch_prompt library snippet.
     * Called from the "Create Draft" button on library cards (v1.8.0).
     */
    public function ajax_create_draft_from_snippet() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( array( 'message' => 'Permission denied.' ) );
        }

        $snippet_id = absint( $_POST['snippet_id'] ?? 0 );
        if ( ! $snippet_id ) {
            wp_send_json_error( array( 'message' => 'Invalid snippet ID.' ) );
        }

        $snippet = Claude_AI_Copywriter_Snippet_Manager::get_snippet( $snippet_id );
        if ( ! $snippet ) {
            wp_send_json_error( array( 'message' => 'Snippet not found.' ) );
        }

        // Derive a readable title from the snippet tags (skip generic ones)
        $tags       = array_map( 'trim', explode( ',', $snippet->tags ?? '' ) );
        $skip_tags  = array( 'KineticLaunch' );
        $title_parts = array_filter( $tags, function( $t ) use ( $skip_tags ) {
            return ! in_array( $t, $skip_tags, true ) && ! empty( $t );
        } );
        $title = ! empty( $title_parts ) ? implode( ' - ', $title_parts ) : __( 'KineticCopy Draft', 'claude-ai-copywriter' );

        // Create a WP draft with the prompt text as initial content
        $post_id = wp_insert_post( array(
            'post_title'   => sanitize_text_field( $title ),
            'post_content' => wp_kses_post( $snippet->prompt_text ?: '' ),
            'post_status'  => 'draft',
            'post_author'  => get_current_user_id(),
        ) );

        if ( is_wp_error( $post_id ) ) {
            wp_send_json_error( array( 'message' => $post_id->get_error_message() ) );
        }

        // Store KineticCopy metadata so the meta box can render it
        // context_data is already decoded to array by get_snippet()
        $cd = is_array( $snippet->context_data ) ? $snippet->context_data : array();

        update_post_meta( $post_id, '_kc_source',      'library' );
        update_post_meta( $post_id, '_kc_prompt',       $snippet->prompt_text );
        update_post_meta( $post_id, '_kc_content_type', 'general' );

        if ( ! empty( $cd['project_name'] ) ) {
            update_post_meta( $post_id, '_kc_project_name', sanitize_text_field( $cd['project_name'] ) );
        }
        if ( ! empty( $cd['prompt_label'] ) ) {
            update_post_meta( $post_id, '_kc_prompt_label', sanitize_text_field( $cd['prompt_label'] ) );
        }
        if ( ! empty( $cd['brand_context'] ) ) {
            update_post_meta( $post_id, '_kc_brand_context', wp_json_encode( $cd['brand_context'] ) );
        }

        // Check if Breakdance is enabled in settings
        $settings        = get_option( 'claude_ai_copywriter_settings', array() );
        $breakdance_url  = null;
        if ( ! empty( $settings['enable_breakdance'] ) ) {
            $breakdance_url = add_query_arg(
                array( 'breakdance' => '1' ),
                get_edit_post_link( $post_id, 'raw' )
            );
        }

        wp_send_json_success( array(
            'post_id'        => $post_id,
            'edit_url'       => get_edit_post_link( $post_id, 'raw' ),
            'breakdance_url' => $breakdance_url,
        ) );
    }

    /* ═══════════════════════════════════════════════════════════
       CONTENT CALENDAR AJAX HANDLERS (v1.9.0)
    ══════════════════════════════════════════════════════════ */

    /**
     * AJAX: Fetch calendar events for a date range.
     * Returns FullCalendar-compatible event objects.
     */
    public function ajax_calendar_events() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );
        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( 'Permission denied.' );
        }

        $start        = sanitize_text_field( wp_unslash( $_POST['start']        ?? '' ) );
        $end          = sanitize_text_field( wp_unslash( $_POST['end']          ?? '' ) );
        $campaign_id  = sanitize_text_field( wp_unslash( $_POST['campaign']     ?? '' ) );
        $status       = sanitize_key(        wp_unslash( $_POST['status']       ?? '' ) );
        $content_type = sanitize_key(        wp_unslash( $_POST['content_type'] ?? '' ) );
        $show_all     = '1' === ( $_POST['show_all'] ?? '0' ); // Show all WP posts, not just KineticCopy

        $args = array(
            'post_type'      => 'post',
            'post_status'    => $status ? array( $status ) : array( 'draft', 'future', 'publish', 'pending' ),
            'posts_per_page' => 300,
        );

        // In "KineticCopy only" mode, restrict to posts with _kc_source meta
        if ( ! $show_all ) {
            $args['meta_query'] = array(
                array(
                    'key'     => '_kc_source',
                    'compare' => 'EXISTS',
                ),
            );
        }

        if ( $start && $end ) {
            $args['date_query'] = array(
                array(
                    'after'     => $start,
                    'before'    => $end,
                    'inclusive' => true,
                ),
            );
        }

        // Content-type filter only applies in KineticCopy-only mode (regular posts don't have this meta)
        if ( $content_type && ! $show_all ) {
            $args['meta_query'][] = array(
                'key'   => '_kc_content_type',
                'value' => $content_type,
            );
        }

        $query  = new WP_Query( $args );
        $events = array();

        // Load campaigns once for color lookup
        $all_campaigns = get_option( 'kc_campaigns', array() );
        $campaign_map  = array();
        foreach ( $all_campaigns as $c ) {
            $campaign_map[ $c['name'] ] = $c;
        }

        foreach ( $query->posts as $post ) {
            $campaign_json  = get_post_meta( $post->ID, '_kc_campaign', true );
            $campaign       = $campaign_json ? json_decode( $campaign_json, true ) : null;
            $source         = get_post_meta( $post->ID, '_kc_source',       true );
            $generated      = (bool) get_post_meta( $post->ID, '_kc_generated',   true );
            $kc_type        = get_post_meta( $post->ID, '_kc_content_type',  true ) ?: 'post';
            $entry_id       = get_post_meta( $post->ID, '_kc_entry_id',      true );

            // Campaign filter
            if ( $campaign_id ) {
                $matched_campaign = false;
                $campaign_name    = $campaign ? ( $campaign['name'] ?? '' ) : '';
                foreach ( $all_campaigns as $c ) {
                    if ( $c['id'] === $campaign_id && $c['name'] === $campaign_name ) {
                        $matched_campaign = true;
                        break;
                    }
                }
                if ( ! $matched_campaign ) {
                    continue;
                }
            }

            // Derive campaign color (from kc_campaigns map if available)
            $campaign_color = null;
            if ( $campaign && ! empty( $campaign['name'] ) && isset( $campaign_map[ $campaign['name'] ] ) ) {
                $campaign_color = $campaign_map[ $campaign['name'] ]['color'] ?? null;
            }

            $has_kc_meta = ! empty( $source );

            // Non-KineticCopy posts (shown in "All Posts" mode) get a neutral grey palette
            if ( $has_kc_meta ) {
                $color = $this->get_calendar_event_color( $post->post_status, $generated, $campaign_color );
            } else {
                $color = array( 'bg' => '#f3f4f6', 'border' => '#9ca3af', 'text' => '#374151' );
            }

            $events[] = array(
                'id'              => $post->ID,
                'title'           => $post->post_title,
                'start'           => get_the_date( 'c', $post ),
                'allDay'          => true,
                'backgroundColor' => $color['bg'],
                'borderColor'     => $color['border'],
                'textColor'       => $color['text'],
                'extendedProps'   => array(
                    'post_id'      => $post->ID,
                    'status'       => $post->post_status,
                    'source'       => $source,
                    'content_type' => $kc_type,
                    'generated'    => $generated,
                    'campaign'     => $campaign ? ( $campaign['name'] ?? '' ) : '',
                    'entry_id'     => $entry_id,
                    'edit_url'     => get_edit_post_link( $post->ID, 'raw' ),
                    'word_count'   => (int) get_post_meta( $post->ID, '_kc_word_count', true ),
                    'has_kc_meta'  => $has_kc_meta,
                ),
            );
        }

        wp_send_json_success( $events );
    }

    /**
     * Derive FullCalendar event colors from post state.
     *
     * @param string      $status         WP post status.
     * @param bool        $generated      Whether content has been generated.
     * @param string|null $campaign_color Hex color from the campaign, or null.
     * @return array  Keys: bg, border, text.
     */
    private function get_calendar_event_color( $status, $generated, $campaign_color ) {
        switch ( $status ) {
            case 'publish':
                return array( 'bg' => '#dcfce7', 'border' => $campaign_color ?: '#16a34a', 'text' => '#15803d' );
            case 'future':
                return array( 'bg' => '#dbeafe', 'border' => $campaign_color ?: '#2563eb', 'text' => '#1d4ed8' );
            case 'draft':
                if ( ! $generated ) {
                    return array( 'bg' => '#fef2f2', 'border' => $campaign_color ?: '#ef4444', 'text' => '#b91c1c' );
                }
                return array( 'bg' => '#fefce8', 'border' => $campaign_color ?: '#eab308', 'text' => '#a16207' );
            default:
                return array( 'bg' => '#f1f5f9', 'border' => $campaign_color ?: '#94a3b8', 'text' => '#475569' );
        }
    }

    /**
     * AJAX: Reschedule a calendar entry (drag-drop).
     */
    public function ajax_calendar_reschedule() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        $post_id  = absint( $_POST['post_id']  ?? 0 );
        $new_date = sanitize_text_field( wp_unslash( $_POST['new_date'] ?? '' ) );

        if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) {
            wp_send_json_error( 'Permission denied.' );
        }
        if ( ! $new_date || ! strtotime( $new_date ) ) {
            wp_send_json_error( 'Invalid date.' );
        }

        // Preserve existing time, only change the date
        $existing_date = get_post_field( 'post_date', $post_id );
        $existing_time = date( 'H:i:s', strtotime( $existing_date ) );
        $full_date     = date( 'Y-m-d', strtotime( $new_date ) ) . ' ' . $existing_time;
        $full_date_gmt = get_gmt_from_date( $full_date );

        $result = wp_update_post( array(
            'ID'           => $post_id,
            'post_date'    => $full_date,
            'post_date_gmt'=> $full_date_gmt,
        ) );

        if ( is_wp_error( $result ) ) {
            wp_send_json_error( $result->get_error_message() );
        }

        // Status corrections after date change
        $post_status = get_post_status( $post_id );
        $is_future   = strtotime( $new_date ) > time();

        if ( 'future' === $post_status && ! $is_future ) {
            // Was scheduled, now moved to past - revert to draft
            wp_update_post( array( 'ID' => $post_id, 'post_status' => 'draft' ) );
        } elseif ( 'draft' === $post_status && $is_future ) {
            // Auto-publish setting: promote draft to scheduled if content exists
            $settings  = get_option( 'claude_ai_copywriter_settings', array() );
            $generated = (bool) get_post_meta( $post_id, '_kc_generated', true );
            if ( ! empty( $settings['kc_auto_publish'] ) && $generated ) {
                wp_update_post( array( 'ID' => $post_id, 'post_status' => 'future' ) );
            }
        }

        // Notify KineticLaunch if this is a Launch-linked entry
        $callback_url = get_post_meta( $post_id, '_kc_callback_url', true );
        $entry_id     = get_post_meta( $post_id, '_kc_entry_id',     true );
        if ( $callback_url && $entry_id ) {
            Claude_AI_Calendar_Push_Receiver::post_callback( $callback_url, array(
                'entry_id'   => $entry_id,
                'status'     => 'rescheduled',
                'wp_post_id' => $post_id,
                'new_date'   => $new_date,
            ) );
        }

        wp_send_json_success( array(
            'post_id'     => $post_id,
            'new_date'    => $full_date,
            'post_status' => get_post_status( $post_id ),
        ) );
    }

    /**
     * AJAX: Create a manual calendar entry (not from KineticLaunch).
     */
    public function ajax_calendar_create_entry() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );
        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( 'Permission denied.' );
        }

        $title        = sanitize_text_field( wp_unslash( $_POST['title']        ?? '' ) );
        $date         = sanitize_text_field( wp_unslash( $_POST['date']         ?? '' ) );
        $content_type = sanitize_key(        wp_unslash( $_POST['content_type'] ?? 'blog_post' ) );
        $brief        = wp_kses_post( wp_unslash( $_POST['brief']               ?? '' ) );
        $campaign_id  = sanitize_text_field( wp_unslash( $_POST['campaign_id']  ?? '' ) );
        $target_words = absint( $_POST['target_words'] ?? 1500 );

        if ( empty( $title ) ) {
            wp_send_json_error( 'Title is required.' );
        }

        $post_date = $date
            ? date( 'Y-m-d 09:00:00', strtotime( $date ) )
            : current_time( 'mysql' );

        $initial_content = '';
        if ( $brief ) {
            $initial_content = '<!-- kineticcopy:pending --><p><em>'
                . esc_html( __( 'Brief: ', 'claude-ai-copywriter' ) )
                . esc_html( wp_trim_words( $brief, 30 ) )
                . '</em></p>';
        }

        $post_id = wp_insert_post( array(
            'post_title'   => $title,
            'post_content' => $initial_content,
            'post_status'  => 'draft',
            'post_author'  => get_current_user_id(),
            'post_date'    => $post_date,
        ) );

        if ( is_wp_error( $post_id ) ) {
            wp_send_json_error( $post_id->get_error_message() );
        }

        update_post_meta( $post_id, '_kc_source',       'manual' );
        update_post_meta( $post_id, '_kc_content_type',  $content_type );
        update_post_meta( $post_id, '_kc_target_words',  $target_words );

        if ( $brief ) {
            update_post_meta( $post_id, '_kc_prompt', $brief );
        }

        if ( $campaign_id ) {
            $campaigns = get_option( 'kc_campaigns', array() );
            foreach ( $campaigns as $c ) {
                if ( $c['id'] === $campaign_id ) {
                    update_post_meta( $post_id, '_kc_campaign', wp_json_encode( $c ) );
                    break;
                }
            }
        }

        wp_send_json_success( array(
            'post_id'  => $post_id,
            'edit_url' => get_edit_post_link( $post_id, 'raw' ),
            'date'     => $post_date,
        ) );
    }

    /**
     * AJAX: Trash (soft-delete) a calendar entry.
     */
    public function ajax_calendar_delete_entry() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );

        $post_id = absint( $_POST['post_id'] ?? 0 );
        if ( ! $post_id || ! current_user_can( 'delete_post', $post_id ) ) {
            wp_send_json_error( 'Permission denied.' );
        }

        $result = wp_trash_post( $post_id );
        if ( ! $result ) {
            wp_send_json_error( 'Failed to delete entry.' );
        }

        wp_send_json_success( array( 'post_id' => $post_id ) );
    }

    /**
     * AJAX: Create or update a campaign.
     */
    public function ajax_campaign_save() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );
        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( 'Permission denied.' );
        }

        $id         = sanitize_text_field( wp_unslash( $_POST['campaign_id'] ?? '' ) );
        $name       = sanitize_text_field( wp_unslash( $_POST['name']        ?? '' ) );
        $color      = sanitize_hex_color(  wp_unslash( $_POST['color']       ?? '#7c3aed' ) );
        $goal       = sanitize_text_field( wp_unslash( $_POST['goal']        ?? '' ) );
        $start_date = sanitize_text_field( wp_unslash( $_POST['start_date']  ?? '' ) );
        $end_date   = sanitize_text_field( wp_unslash( $_POST['end_date']    ?? '' ) );

        if ( empty( $name ) ) {
            wp_send_json_error( 'Campaign name is required.' );
        }

        $campaigns = get_option( 'kc_campaigns', array() );
        $is_new    = empty( $id );

        if ( $is_new ) {
            $id = 'camp_' . uniqid();
            $campaigns[] = array(
                'id'         => $id,
                'name'       => $name,
                'color'      => $color ?: '#7c3aed',
                'goal'       => $goal,
                'start_date' => $start_date,
                'end_date'   => $end_date,
                'source'     => 'manual',
                'created_at' => gmdate( 'c' ),
            );
        } else {
            foreach ( $campaigns as &$c ) {
                if ( $c['id'] === $id ) {
                    $c['name']       = $name;
                    $c['color']      = $color ?: ( $c['color'] ?? '#7c3aed' );
                    $c['goal']       = $goal;
                    $c['start_date'] = $start_date;
                    $c['end_date']   = $end_date;
                    break;
                }
            }
            unset( $c );
        }

        update_option( 'kc_campaigns', $campaigns );

        wp_send_json_success( array(
            'campaign_id' => $id,
            'campaigns'   => array_values( $campaigns ),
        ) );
    }

    /**
     * AJAX: Delete a manual campaign (KineticLaunch campaigns are not deletable via this endpoint).
     */
    public function ajax_campaign_delete() {
        check_ajax_referer( 'claude_ai_nonce', 'nonce' );
        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( 'Permission denied.' );
        }

        $id = sanitize_text_field( wp_unslash( $_POST['campaign_id'] ?? '' ) );
        if ( ! $id ) {
            wp_send_json_error( 'Campaign ID required.' );
        }

        $campaigns = get_option( 'kc_campaigns', array() );

        // Prevent deleting KineticLaunch-sourced campaigns via this endpoint
        foreach ( $campaigns as $c ) {
            if ( $c['id'] === $id && ( $c['source'] ?? '' ) === 'kineticlaunch' ) {
                wp_send_json_error( 'KineticLaunch campaigns cannot be deleted here.' );
            }
        }

        $campaigns = array_values( array_filter( $campaigns, function ( $c ) use ( $id ) {
            return $c['id'] !== $id;
        } ) );

        update_option( 'kc_campaigns', $campaigns );

        wp_send_json_success( array( 'campaigns' => $campaigns ) );
    }

    /**
     * AJAX: Generic settings save for boolean/toggle settings.
     * Currently handles: kc_auto_generate_on_push, kc_auto_publish (v1.8.0).
     * Extend the allowed keys list below to add new settings over time.
     */
    public function ajax_save_settings() {
        check_ajax_referer( 'claude_ai_settings_save', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( 'Unauthorized.' );
        }

        // Whitelist of settings keys this handler is allowed to update
        $allowed_keys = array(
            'kc_auto_generate_on_push',
            'kc_auto_publish',
        );

        $settings = get_option( 'claude_ai_copywriter_settings', array() );

        foreach ( $allowed_keys as $key ) {
            if ( isset( $_POST[ $key ] ) ) {
                $settings[ $key ] = (bool) intval( $_POST[ $key ] );
            }
        }

        update_option( 'claude_ai_copywriter_settings', $settings );
        wp_send_json_success( array( 'message' => 'Settings saved.' ) );
    }

    /**
     * Detect prompt injection attempts in user input.
     * Mirrors the same guard in Claude_AI_Copywriter_Chat_Assistant.
     *
     * @param string $text User-supplied text to check.
     * @return bool True if an injection attempt is detected.
     */
    private function contains_prompt_injection( $text ) {
        $patterns = array(
            '/ignore\s+(all\s+)?(previous|prior|above|earlier)\s+(instructions?|prompts?|rules?|guidelines?)/i',
            '/forget\s+(everything|all|your|previous|prior)\s*(instructions?|prompts?|rules?)?/i',
            '/disregard\s+(all\s+)?(previous|prior|above)\s+(instructions?|prompts?)/i',
            '/override\s+(your\s+)?(instructions?|system\s+prompt|rules?|guidelines?)/i',
            '/you\s+are\s+now\s+(a\s+)?(?!a\s+content|an?\s+AI\s+content)/i',
            '/act\s+as\s+(if\s+you\s+are\s+)?(a\s+)?(DAN|jailbreak|evil|unrestricted|unfiltered)/i',
            '/pretend\s+(you\s+(have|are|don\'?t))/i',
            '/reveal\s+(your\s+)?(system\s+prompt|instructions?|rules?|guidelines?|prompt)/i',
            '/print\s+(your\s+)?(system\s+prompt|instructions?)/i',
            '/repeat\s+(your\s+)?(system\s+prompt|instructions?|everything\s+above)/i',
            '/\bDAN\b/',
            '/jailbreak/i',
            '/developer\s+mode/i',
        );
        foreach ( $patterns as $pattern ) {
            if ( preg_match( $pattern, $text ) ) {
                return true;
            }
        }
        return false;
    }

    /**
     * Output the KineticCopy onboarding wizard modal.
     * Only rendered on KineticCopy admin pages.
     * Dismissal state is stored in localStorage (no DB overhead).
     * Can be restarted from Settings > General via a button.
     */
    public function output_onboarding_wizard() {
        $screen = get_current_screen();
        if ( ! $screen || strpos( $screen->id, 'kineticcopy' ) === false ) {
            return;
        }

        $studio_url   = admin_url( 'admin.php?page=kineticcopy' );
        $library_url  = admin_url( 'admin.php?page=kineticcopy-library' );
        $settings_url = admin_url( 'admin.php?page=kineticcopy-settings' );
        $api_url      = esc_url( $settings_url . '#api-key' );
        $integ_url    = esc_url( $settings_url . '#api-key' ); // integrations subtab inside api-key tab

        $steps = array(
            array(
                'icon'    => '🎉',
                'title'   => __( 'Welcome to KineticCopy!', 'claude-ai-copywriter' ),
                'desc'    => __( 'You\'re moments away from AI-powered copywriting inside WordPress. This quick walkthrough takes about 2 minutes — skip it anytime, or restart it from Settings later.', 'claude-ai-copywriter' ),
                'video'   => true,
                'action'  => '',
                'action_label' => '',
            ),
            array(
                'icon'    => '🔑',
                'title'   => __( 'Step 1 — Add Your API Key', 'claude-ai-copywriter' ),
                'desc'    => __( 'KineticCopy is BYOK (bring your own key). Go to <strong>Settings → API Keys</strong> and paste your Anthropic API key. Click <strong>Test Key</strong> to verify, then <strong>Save Key</strong>. Your key is stored encrypted — we never see it.', 'claude-ai-copywriter' ),
                'video'   => true,
                'action'  => $api_url,
                'action_label' => __( 'Go to API Settings →', 'claude-ai-copywriter' ),
            ),
            array(
                'icon'    => '🔗',
                'title'   => __( 'Step 2 — Connect KineticLaunch™ (Optional)', 'claude-ai-copywriter' ),
                'desc'    => __( 'If you use <a href="https://launch.kineticbrain.ai" target="_blank">KineticLaunch™</a> for brand strategy, connect it here and KineticCopy will automatically use your brand voice, personas, and content calendar. You can skip this and connect later.', 'claude-ai-copywriter' ),
                'video'   => true,
                'action'  => $integ_url,
                'action_label' => __( 'Go to Integrations →', 'claude-ai-copywriter' ),
            ),
            array(
                'icon'    => '✍️',
                'title'   => __( 'Step 3 — Generate Your First Copy', 'claude-ai-copywriter' ),
                'desc'    => __( 'Open the <strong>Copywriting Studio</strong>. Pick a content type (blog post, email, social post…), choose your tone blend, and hit <strong>Generate</strong>. Your polished copy appears in seconds — edit it, copy it, or save it to your Library.', 'claude-ai-copywriter' ),
                'video'   => true,
                'action'  => esc_url( $studio_url ),
                'action_label' => __( 'Open Studio →', 'claude-ai-copywriter' ),
            ),
            array(
                'icon'    => '📚',
                'title'   => __( 'Step 4 — Save to Your Content Library', 'claude-ai-copywriter' ),
                'desc'    => __( 'After generating, click <strong>Save to Library</strong> to keep your best copy. Organize it with folders, rate your favorites with stars, and <strong>Run Again</strong> anytime to reload a prompt. Your library grows with you.', 'claude-ai-copywriter' ),
                'video'   => true,
                'action'  => esc_url( $library_url ),
                'action_label' => __( 'Go to Library →', 'claude-ai-copywriter' ),
            ),
            array(
                'icon'    => '🧠',
                'title'   => __( 'Step 5 — Meet the BRAIN Assistant', 'claude-ai-copywriter' ),
                'desc'    => __( 'See the round icon on the right side of your screen? That\'s BRAIN — your strategic copy partner. After setting up your API key, BRAIN can answer how-to questions, guide your content strategy, and act as a sounding board for your brand voice. Ask it anything: "what tone suits a luxury brand?", "rewrite this for a younger audience", or "help me plan a launch email". It knows your brand context automatically.', 'claude-ai-copywriter' ),
                'video'   => true,
                'action'  => '',
                'action_label' => '',
            ),
            array(
                'icon'    => '🚀',
                'title'   => __( 'You\'re All Set!', 'claude-ai-copywriter' ),
                'desc'    => __( 'KineticCopy is ready to go. Explore the <strong>Content Calendar</strong>, build your <strong>Writing DNA</strong> brand profile in My Brand, or dive straight into the Studio. The full manual is always available in <strong>Settings → Manual</strong>.', 'claude-ai-copywriter' ),
                'video'   => false,
                'action'  => esc_url( $studio_url ),
                'action_label' => __( 'Start Creating →', 'claude-ai-copywriter' ),
            ),
        );

        $total = count( $steps );
        ?>
        <!-- KineticCopy Onboarding Wizard -->
        <div id="kc-onboarding-overlay" style="display:none;" role="dialog" aria-modal="true" aria-labelledby="kc-onboarding-title">
            <div id="kc-onboarding-modal">
                <button type="button" id="kc-onboarding-close" aria-label="<?php esc_attr_e( 'Close onboarding', 'claude-ai-copywriter' ); ?>">
                    <span aria-hidden="true">&times;</span>
                </button>

                <!-- Steps -->
                <?php foreach ( $steps as $i => $step ) :
                    $step_num = $i + 1;
                    $is_first = ( 0 === $i );
                    $is_last  = ( $i === $total - 1 );
                ?>
                <div class="kc-onboarding-step<?php echo $is_first ? ' kc-ob-active' : ''; ?>" data-step="<?php echo $step_num; ?>">
                    <div class="kc-ob-icon"><?php echo $step['icon']; ?></div>
                    <h2 id="kc-onboarding-title" class="kc-ob-title"><?php echo esc_html( $step['title'] ); ?></h2>
                    <p class="kc-ob-desc"><?php echo wp_kses( $step['desc'], array( 'strong'=>array(), 'a'=>array('href'=>array(),'target'=>array()), 'em'=>array() ) ); ?></p>

                    <?php if ( $step['video'] ) : ?>
                    <div class="kc-ob-video-slot" aria-label="<?php esc_attr_e( 'Video tutorial placeholder', 'claude-ai-copywriter' ); ?>">
                        <div class="kc-ob-video-placeholder">
                            <span class="kc-ob-play-icon">▶</span>
                            <span><?php _e( 'Video coming soon', 'claude-ai-copywriter' ); ?></span>
                        </div>
                    </div>
                    <?php endif; ?>

                    <?php if ( $step['action'] && $step['action_label'] ) : ?>
                    <a href="<?php echo $step['action']; ?>" class="kc-ob-action-btn" target="<?php echo strpos( $step['action'], admin_url() ) === 0 ? '_self' : '_blank'; ?>">
                        <?php echo esc_html( $step['action_label'] ); ?>
                    </a>
                    <?php endif; ?>
                </div>
                <?php endforeach; ?>

                <!-- Footer: progress + nav -->
                <div id="kc-ob-footer">
                    <div id="kc-ob-dots">
                        <?php for ( $d = 1; $d <= $total; $d++ ) : ?>
                        <span class="kc-ob-dot<?php echo ( 1 === $d ) ? ' kc-ob-dot-active' : ''; ?>" data-dot="<?php echo $d; ?>"></span>
                        <?php endfor; ?>
                    </div>
                    <div id="kc-ob-nav">
                        <button type="button" id="kc-ob-prev" disabled><?php _e( '← Back', 'claude-ai-copywriter' ); ?></button>
                        <button type="button" id="kc-ob-next"><?php _e( 'Next →', 'claude-ai-copywriter' ); ?></button>
                        <button type="button" id="kc-ob-finish" style="display:none;"><?php _e( '✓ Get Started', 'claude-ai-copywriter' ); ?></button>
                    </div>
                </div>
            </div>
        </div>

        <style id="kc-onboarding-css">
        #kc-onboarding-overlay {
            position: fixed; inset: 0; z-index: 999999;
            background: rgba(0,0,0,.55);
            display: flex; align-items: center; justify-content: center;
            padding: 16px; box-sizing: border-box;
        }
        #kc-onboarding-modal {
            background: #fff; border-radius: 12px;
            box-shadow: 0 16px 48px rgba(0,0,0,.22);
            max-width: 540px; width: 100%;
            max-height: calc(100vh - 32px);
            overflow-y: auto; overflow-x: hidden;
            padding: 32px 32px 24px;
            position: relative; box-sizing: border-box;
        }
        #kc-onboarding-close {
            position: absolute; top: 14px; right: 16px;
            background: none; border: none; cursor: pointer;
            font-size: 22px; color: #9ca3af; line-height: 1; padding: 4px;
            transition: color .15s;
        }
        #kc-onboarding-close:hover { color: #374151; }
        .kc-onboarding-step { display: none; animation: kcObFade .25s ease; }
        .kc-onboarding-step.kc-ob-active { display: block; }
        @keyframes kcObFade { from { opacity:0; transform: translateY(6px); } to { opacity:1; transform: none; } }
        .kc-ob-icon { font-size: 40px; margin-bottom: 10px; line-height: 1; }
        .kc-ob-title {
            font-family: 'Instrument Serif', Georgia, serif;
            font-size: 22px; color: #111827; margin: 0 0 10px; font-weight: 400;
        }
        .kc-ob-desc {
            font-size: 14px; color: #374151; line-height: 1.65;
            margin: 0 0 18px;
        }
        .kc-ob-desc a { color: #D6263A; }
        .kc-ob-video-slot {
            background: #f8f8f8; border: 2px dashed #e5e7eb;
            border-radius: 8px; margin-bottom: 18px; overflow: hidden;
            aspect-ratio: 16/9; display: flex; align-items: center; justify-content: center;
        }
        .kc-ob-video-placeholder {
            display: flex; flex-direction: column; align-items: center; gap: 8px;
            color: #9ca3af; font-size: 13px;
        }
        .kc-ob-play-icon {
            display: flex; align-items: center; justify-content: center;
            width: 48px; height: 48px; border-radius: 50%;
            background: #e5e7eb; color: #9ca3af; font-size: 18px;
        }
        .kc-ob-action-btn {
            display: inline-block; padding: 9px 18px;
            background: #D6263A; color: #fff !important;
            border-radius: 6px; font-size: 13px; font-weight: 600;
            text-decoration: none; margin-bottom: 18px;
            transition: background .15s;
        }
        .kc-ob-action-btn:hover { background: #A6172A; }
        #kc-ob-footer {
            display: flex; align-items: center; justify-content: space-between;
            margin-top: 12px; padding-top: 16px;
            border-top: 1px solid #f0f0f0; gap: 12px; flex-wrap: wrap;
        }
        #kc-ob-dots { display: flex; gap: 6px; align-items: center; }
        .kc-ob-dot {
            width: 8px; height: 8px; border-radius: 50%;
            background: #e5e7eb; transition: background .2s, transform .2s;
        }
        .kc-ob-dot-active { background: #D6263A; transform: scale(1.3); }
        #kc-ob-nav { display: flex; gap: 8px; }
        #kc-ob-prev, #kc-ob-next, #kc-ob-finish {
            padding: 7px 16px; border-radius: 6px; font-size: 13px;
            font-weight: 600; cursor: pointer; transition: all .15s;
        }
        #kc-ob-prev {
            background: #f6f7f7; border: 1px solid #dcdcde; color: #374151;
        }
        #kc-ob-prev:hover:not([disabled]) { background: #e9eaea; }
        #kc-ob-prev[disabled] { opacity: .4; cursor: not-allowed; }
        #kc-ob-next {
            background: #111827; border: 1px solid #111827; color: #fff;
        }
        #kc-ob-next:hover { background: #1f2937; }
        #kc-ob-finish {
            background: #D6263A; border: 1px solid #A6172A; color: #fff;
        }
        #kc-ob-finish:hover { background: #A6172A; }
        /* Mobile */
        @media (max-width: 600px) {
            #kc-onboarding-modal { padding: 24px 20px 20px; }
            .kc-ob-title { font-size: 18px; }
            .kc-ob-desc { font-size: 13px; }
            #kc-ob-footer { flex-direction: column; align-items: center; }
            #kc-ob-nav { width: 100%; justify-content: space-between; }
        }
        </style>

        <script id="kc-onboarding-js">
        (function($) {
            var LS_KEY      = 'kc_onboarding_dismissed';
            var LS_STEP_KEY = 'kc_onboarding_step';
            var TOTAL_STEPS = <?php echo $total; ?>;
            var current     = 1;

            // Don't show if already dismissed
            try { if (localStorage.getItem(LS_KEY) === '1') return; } catch(e) {}

            // Map step number → WP page slug (for background page sync)
            // Steps: 1=Welcome, 2=API Key, 3=KineticLaunch, 4=Studio, 5=Library, 6=Brain, 7=All Set
            var STEP_SLUGS = <?php
                $slug_map = array();
                foreach ( $steps as $i => $step ) {
                    // Extract 'page' param from action URL if it's a WP admin URL
                    $page_slug = '';
                    if ( ! empty( $step['action'] ) ) {
                        parse_str( wp_parse_url( $step['action'], PHP_URL_QUERY ), $q );
                        $page_slug = isset( $q['page'] ) ? $q['page'] : '';
                    }
                    $slug_map[] = $page_slug;
                }
                echo wp_json_encode( $slug_map );
            ?>;

            // Get current page slug
            var currentPage = (function() {
                var params = new URLSearchParams(window.location.search);
                return params.get('page') || '';
            })();

            // Restore saved step (so manual nav doesn't restart from step 1)
            var savedStep = 1;
            try {
                var ss = parseInt(localStorage.getItem(LS_STEP_KEY));
                if (ss >= 1 && ss <= TOTAL_STEPS) savedStep = ss;
            } catch(e) {}

            // Show the overlay
            $('#kc-onboarding-overlay').fadeIn(300);

            function goTo(step) {
                if (step < 1 || step > TOTAL_STEPS) return;
                $('.kc-onboarding-step').removeClass('kc-ob-active');
                $('.kc-onboarding-step[data-step="' + step + '"]').addClass('kc-ob-active');
                $('.kc-ob-dot').removeClass('kc-ob-dot-active');
                $('.kc-ob-dot[data-dot="' + step + '"]').addClass('kc-ob-dot-active');
                $('#kc-ob-prev').prop('disabled', step === 1);
                if (step === TOTAL_STEPS) {
                    $('#kc-ob-next').hide();
                    $('#kc-ob-finish').show();
                } else {
                    $('#kc-ob-next').show();
                    $('#kc-ob-finish').hide();
                }
                current = step;
                // Save step so it survives page navigation
                try { localStorage.setItem(LS_STEP_KEY, step); } catch(e) {}
                // Scroll to top of modal
                $('#kc-onboarding-modal').scrollTop(0);

                // Background page sync: navigate to the step's page if different
                var targetSlug = STEP_SLUGS[step - 1] || '';
                if (targetSlug && targetSlug !== currentPage) {
                    // Navigate after a short delay so user sees the step transition first
                    setTimeout(function() {
                        window.location.href = '<?php echo esc_js( admin_url( 'admin.php?page=' ) ); ?>' + encodeURIComponent(targetSlug);
                    }, 600);
                }
            }

            function dismiss() {
                try {
                    localStorage.setItem(LS_KEY, '1');
                    localStorage.removeItem(LS_STEP_KEY); // Clear step on full dismiss
                } catch(e) {}
                $('#kc-onboarding-overlay').fadeOut(250);
            }

            // Restore to saved step on load (fixes "restart on manual nav" bug)
            if (savedStep > 1) {
                goTo(savedStep);
            }

            $('#kc-ob-next').on('click', function() { goTo(current + 1); });
            $('#kc-ob-prev').on('click', function() { goTo(current - 1); });
            $('#kc-ob-finish').on('click', dismiss);
            $('#kc-onboarding-close').on('click', dismiss);
            // Click-outside-modal to close
            $('#kc-onboarding-overlay').on('click', function(e) {
                if ($(e.target).is('#kc-onboarding-overlay')) dismiss();
            });
            // Keyboard: Escape to close, arrow keys to navigate
            $(document).on('keydown', function(e) {
                if (!$('#kc-onboarding-overlay').is(':visible')) return;
                if (e.key === 'Escape') { dismiss(); }
                if (e.key === 'ArrowRight') { goTo(current + 1); }
                if (e.key === 'ArrowLeft')  { goTo(current - 1); }
            });
            // Progress dot click
            $(document).on('click', '.kc-ob-dot', function() { goTo(parseInt($(this).data('dot'))); });

            // Restart trigger (called from Settings page button)
            window.kcRestartOnboarding = function() {
                try {
                    localStorage.removeItem(LS_KEY);
                    localStorage.removeItem(LS_STEP_KEY);
                } catch(e) {}
                goTo(1);
                $('#kc-onboarding-overlay').fadeIn(300);
            };
        })(jQuery);
        </script>
        <?php
    }
}
