<?php
/**
 * =====================================================================
 *  WordAI Agent — Central AJAX Handler
 *  -------------------------------------------------------------------
 *  Handles all admin-side AJAX actions for the WordAI Agent module:
 *   - Cost estimation before embedding
 *   - Start indexing (real embeddings via OpenAI / Gemini / others)
 *   - Background indexing runs (via WP-Cron or inline fallback)
 *   - Progress, summary, failed items, and chat settings
 *
 *  Provider-aware (Option B):
 *   - Each provider indexes the same content separately (per-provider)
 *   - First time a provider runs → indexes all matching posts
 *     even if another provider already indexed them
 *   - Subsequent runs for the same provider:
 *       • Only posts whose content changed since last embedding
 *         for that provider
 *       • Or posts that previously failed/cancelled
 * =====================================================================
 *  @since 1.0.0
 */

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

class SftcyWordaiOpenaiAgentAjax {

    private static $initiated = false;

    public function __construct() {
        if ( ! self::$initiated ) {
            self::initiate_hooks();
        }
    }

    /**
     * -----------------------------------------------------------------
     * Hook initialization: enqueue + AJAX endpoints
     * -----------------------------------------------------------------
     */
    private static function initiate_hooks() {

        // --- Background indexing event (via WP-Cron) ---
        // Passing one argument (array) with post_types + embedding_model.
        add_action( 'sc_wordai_agent_run_indexing_event', [ __CLASS__, 'run_indexing' ], 10, 1 );

        // --- AJAX: Estimation ---
        add_action( 'wp_ajax_sc_wordai_agent_estimate_index_cost', [ __CLASS__, 'estimate_index_cost' ] );

        // --- AJAX: Indexing Control ---
        add_action( 'wp_ajax_sc_wordai_agent_start_indexing',   [ __CLASS__, 'start_indexing' ] );
        add_action( 'wp_ajax_sc_wordai_agent_run_indexing',     [ __CLASS__, 'run_indexing' ] ); // optional direct call
        add_action( 'wp_ajax_sc_wordai_agent_cancel_indexing',  [ __CLASS__, 'cancel_indexing' ] );

        // --- AJAX: Progress + Summary ---
        add_action( 'wp_ajax_sc_wordai_agent_get_progress',     [ __CLASS__, 'sc_wordai_agent_get_progress' ] );
        add_action( 'wp_ajax_sc_wordai_agent_get_summary',      [ __CLASS__, 'get_summary' ] );
        add_action( 'wp_ajax_sc_wordai_agent_get_failed_items', [ __CLASS__, 'get_failed_items' ] );
        add_action( 'wp_ajax_sc_wordai_agent_get_source_items', [ __CLASS__, 'get_source_items' ] );
        add_action( 'wp_ajax_sc_wordai_agent_update_source_selection', [ __CLASS__, 'update_source_selection' ] );

        // --- AJAX: AI Chat Settings ---
        add_action( 'wp_ajax_sc_wordai_agent_save_chat_settings',  [ __CLASS__, 'sc_wordai_agent_save_chat_settings' ] );
        add_action( 'wp_ajax_sc_wordai_agent_reset_chat_settings', [ __CLASS__, 'sc_wordai_agent_reset_chat_settings' ] );
        add_action( 'wp_ajax_sc_wordai_agent_save_anthropic_embedding_settings', [ __CLASS__, 'sc_wordai_agent_save_anthropic_embedding_settings' ] );
        add_action( 'wp_ajax_sc_wordai_agent_update_lead_status', [ __CLASS__, 'sc_wordai_agent_update_lead_status' ] );
        add_action( 'wp_ajax_sc_wordai_agent_delete_lead', [ __CLASS__, 'sc_wordai_agent_delete_lead' ] );
        add_action( 'wp_ajax_sc_wordai_agent_export_leads_csv', [ __CLASS__, 'sc_wordai_agent_export_leads_csv' ] );

        self::$initiated = true;
    }

    /**
     * Get the current Embedding Agent class based on active provider.
     *
     * Uses SftcyWordaiBase::sc_wordai_get_embedding_agent_class():
     *   - 'openai' -> SftcyWordaiAgentOpenai
     *   - 'google' -> SftcyWordaiAgentGemini
     *
     * @return string|null
     */
    private static function get_current_agent_class() {
        if ( ! class_exists( 'SftcyWordaiBase' ) || ! method_exists( 'SftcyWordaiBase', 'sc_wordai_get_embedding_agent_class' ) ) {
            return null;
        }
        $agent_class = SftcyWordaiBase::sc_wordai_get_embedding_agent_class();
        if ( ! $agent_class || ! class_exists( $agent_class ) ) {
            return null;
        }
        return $agent_class;
    }

    /**
     * Get the current provider slug ('openai', 'google', etc.).
     *
     * @return string
     */
    private static function get_current_provider_slug() {
        if ( class_exists( 'SftcyWordaiBase' ) && method_exists( 'SftcyWordaiBase', 'sc_wordai_get_current_ai_provider' ) ) {
            $p = SftcyWordaiBase::sc_wordai_get_current_ai_provider();
            if ( ! empty( $p ) ) {
                return $p;
            }
        }
        return 'openai'; // sensible fallback
    }

    /**
     * ==================================================================
     * Provider-aware Pending Source Resolver  (CORE of Option B)
     * ------------------------------------------------------------------
     *  - Returns sources that NEED (re-)embedding for a given provider.
     *  - Combines:
     *      • Posts that have NEVER been embedded by this provider
     *      • Posts whose WP content was modified AFTER last embedding
     *        by this provider
     *      • Posts flagged as 'ready', 'failed', or 'cancelled'
     *  - Ignores 'archived' sources
     *
     *  This is used by:
     *      - estimate_index_cost()
     *      - start_indexing()  (for total/ready count)
     *      - run_indexing()    (for actual work)
     *
     * @param array  $post_types  WP post types to consider
     * @param string $provider    'openai' | 'google' | etc.
     * @param int    $limit       Optional hard cap (0 = no limit)
     * @return array of stdClass rows from wp_wordai_agent_sources
     * ==================================================================
     */
    private static function get_pending_sources_for_provider( array $post_types, $provider, $limit = 0 ) {
        global $wpdb;

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

        $sources_table = $wpdb->prefix . 'wordai_agent_sources';
        $embeds_table  = $wpdb->prefix . 'wordai_agent_embeddings';
        $selection_table = $wpdb->prefix . 'wordai_agent_source_selection';
        $posts_table   = $wpdb->posts;

        // Prepare placeholders for IN (...)
        $placeholders = implode( ',', array_fill( 0, count( $post_types ), '%s' ) );

        // Params order:
        //   1) provider (for subquery)
        //   2) post_types...
        //   3) optional limit
        $params = array_merge( [ $provider ], $post_types );
        $limit_sql = '';

        if ( $limit > 0 ) {
            $limit_sql = ' LIMIT %d';
            $params[]  = (int) $limit;
        }

        /**
         * Logic:
         *   LEFT JOIN a subquery that aggregates last embedding time per
         *   (provider, source_id).
         *
         *   A source is pending for provider P if:
         *      - Not archived
         *      - AND (
         *           e.last_embed IS NULL                        => never embedded by P
         *        OR p.post_modified > e.last_embed              => content changed since last embed by P
         *        OR s.status IN ('ready','cancelled','failed')  => explicitly flagged
         *        )
         */
        $sql = "
            SELECT s.*
            FROM {$sources_table} AS s
            INNER JOIN {$posts_table} AS p
                ON p.ID = s.source_id
            INNER JOIN {$selection_table} AS sel
                ON sel.source_row_id = s.id
               AND sel.selected = 1
            LEFT JOIN (
                SELECT source_id, MAX(created_at) AS last_embed
                FROM {$embeds_table}
                WHERE provider = %s
                GROUP BY source_id
            ) AS e
                ON e.source_id = s.source_id
            WHERE s.source_type IN ({$placeholders})
              AND s.status <> 'archived'
              AND (
                    e.last_embed IS NULL
                 OR p.post_modified > e.last_embed
                 OR s.status IN ('ready','cancelled','failed')
              )
            ORDER BY s.id ASC
            {$limit_sql}
        ";

        $rows = $wpdb->get_results( $wpdb->prepare( $sql, $params ) );

        if ( ! is_array( $rows ) ) {
            return [];
        }

        return $rows;
    }

    private static function get_embedding_item_limit() {
        if ( class_exists( 'SftcyWordaiFeatures' ) ) {
            return (int) SftcyWordaiFeatures::wordai_get_embedding_item_limit();
        }

        return 0;
    }

    private static function get_embedding_limit_message() {
        $limit = self::get_embedding_item_limit();

        if ( $limit <= 0 ) {
            return __( 'Upgrade to WordAI Pro to unlock a larger AI Agent knowledge base.', 'wordai' );
        }

        return sprintf(
            __( 'You have embedded your allowed %d content items already. To embed more or unlimited posts, pages, products, or CPT contents, upgrade to WordAI Pro.', 'wordai' ),
            $limit
        );
    }

    public static function get_embedded_item_count() {
        global $wpdb;

        $tbl_embeds = $wpdb->prefix . 'wordai_agent_embeddings';
        return (int) $wpdb->get_var( "SELECT COUNT(DISTINCT source_id) FROM {$tbl_embeds}" );
    }

    private static function get_indexed_source_ids() {
        global $wpdb;

        $tbl_embeds = $wpdb->prefix . 'wordai_agent_embeddings';
        $rows       = $wpdb->get_col( "SELECT DISTINCT source_id FROM {$tbl_embeds}" );

        if ( ! is_array( $rows ) ) {
            return array();
        }

        return array_map( 'intval', $rows );
    }

    private static function get_selected_source_count() {
        global $wpdb;

        $tbl_sources   = $wpdb->prefix . 'wordai_agent_sources';
        $tbl_selection = $wpdb->prefix . 'wordai_agent_source_selection';

        return (int) $wpdb->get_var(
            "SELECT COUNT(*)
             FROM {$tbl_selection} AS sel
             INNER JOIN {$tbl_sources} AS s ON s.id = sel.source_row_id
             WHERE sel.selected = 1
               AND s.status <> 'archived'"
        );
    }

    private static function enforce_selection_limit() {
        global $wpdb;

        $limit = self::get_embedding_item_limit();
        if ( $limit <= 0 ) {
            return;
        }

        $tbl_sources   = $wpdb->prefix . 'wordai_agent_sources';
        $tbl_selection = $wpdb->prefix . 'wordai_agent_source_selection';

        $selected_ids = $wpdb->get_col(
            "SELECT sel.source_row_id
             FROM {$tbl_selection} AS sel
             INNER JOIN {$tbl_sources} AS s ON s.id = sel.source_row_id
             WHERE sel.selected = 1
               AND s.status <> 'archived'
             ORDER BY sel.updated_at DESC, sel.id DESC
             LIMIT 100000"
        );

        if ( ! is_array( $selected_ids ) || count( $selected_ids ) <= $limit ) {
            return;
        }

        $keep = array_slice( array_map( 'intval', $selected_ids ), 0, $limit );
        if ( empty( $keep ) ) {
            $wpdb->query( "UPDATE {$tbl_selection} SET selected = 0, updated_at = NOW()" );
            return;
        }

        $keep_sql = implode( ',', $keep );
        $wpdb->query(
            "UPDATE {$tbl_selection}
             SET selected = 0, updated_at = NOW()
             WHERE selected = 1
               AND source_row_id NOT IN ({$keep_sql})"
        );
    }

    private static function ensure_default_selection_for_types( array $post_types ) {
        global $wpdb;

        if ( empty( $post_types ) ) {
            return;
        }

        $post_types = array_values( array_filter( array_map( 'sanitize_key', $post_types ) ) );
        if ( empty( $post_types ) ) {
            return;
        }

        $tbl_sources   = $wpdb->prefix . 'wordai_agent_sources';
        $tbl_selection = $wpdb->prefix . 'wordai_agent_source_selection';
        $placeholders  = implode( ',', array_fill( 0, count( $post_types ), '%s' ) );

        $missing_rows = $wpdb->get_col(
            $wpdb->prepare(
                "SELECT s.id
                 FROM {$tbl_sources} AS s
                 INNER JOIN {$wpdb->posts} AS p ON p.ID = s.source_id
                 LEFT JOIN {$tbl_selection} AS sel ON sel.source_row_id = s.id
                 WHERE s.source_type IN ({$placeholders})
                   AND s.status <> 'archived'
                   AND sel.id IS NULL
                 ORDER BY p.post_modified DESC, s.id DESC",
                $post_types
            )
        );

        if ( ! is_array( $missing_rows ) || empty( $missing_rows ) ) {
            self::enforce_selection_limit();
            return;
        }

        $limit          = self::get_embedding_item_limit();
        $selected_count = self::get_selected_source_count();

        foreach ( $missing_rows as $source_row_id ) {
            $selected = 1;
            if ( $limit > 0 ) {
                $selected = $selected_count < $limit ? 1 : 0;
                if ( $selected ) {
                    $selected_count++;
                }
            }

            $wpdb->query(
                $wpdb->prepare(
                    "INSERT IGNORE INTO {$tbl_selection}
                        (source_row_id, selected, created_at, updated_at)
                     VALUES (%d, %d, %s, %s)",
                    (int) $source_row_id,
                    $selected,
                    current_time( 'mysql' ),
                    current_time( 'mysql' )
                )
            );
        }

        self::enforce_selection_limit();
    }

    private static function apply_embedding_item_limit_to_rows( array $rows, $provider ) {
        $limit = self::get_embedding_item_limit();

        if ( $limit <= 0 || empty( $rows ) ) {
            return $rows;
        }

        $indexed_ids     = self::get_indexed_source_ids();
        $indexed_lookup  = array_fill_keys( $indexed_ids, true );
        $remaining_slots = max( 0, $limit - count( $indexed_ids ) );
        $filtered_rows   = array();

        foreach ( $rows as $row ) {
            $source_id = isset( $row->source_id ) ? (int) $row->source_id : 0;

            if ( isset( $indexed_lookup[ $source_id ] ) ) {
                $filtered_rows[] = $row;
                continue;
            }

            if ( $remaining_slots > 0 ) {
                $filtered_rows[] = $row;
                $remaining_slots--;
            }
        }

        return $filtered_rows;
    }

    /**
     * ===========================================================
     * Estimate total tokens & USD cost before embedding
     * -----------------------------------------------------------
     * Provider-aware:
     *  - Only counts posts that need (re-)indexing for the
     *    CURRENT provider (Option B behaviour).
     * ===========================================================
     */
    public static function estimate_index_cost() {
        check_ajax_referer( 'scwordai_wpnonce', 'security' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => __( 'Permission denied.', 'wordai-pro' ),
            ] );
        }

        $agent_class = self::get_current_agent_class();
        if ( ! $agent_class ) {
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => __( 'No valid embedding agent class found for the current provider.', 'wordai-pro' ),
            ] );
        }

        if ( method_exists( $agent_class, 'is_configured' ) && ! call_user_func( [ $agent_class, 'is_configured' ] ) ) {
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => __( 'Please save the required embedding API key before starting indexing for this provider.', 'wordai-pro' ),
            ] );
        }

        $provider = self::get_current_provider_slug();

        global $wpdb;

        // 🧩 Selected post types from UI
        $post_types = isset( $_POST['post_types'] ) && is_array( $_POST['post_types'] )
            ? array_map( 'sanitize_text_field', $_POST['post_types'] )
            : [ 'post' ];

        if ( empty( $post_types ) ) {
            wp_send_json( [ 'status' => 'fail', 'feedbackMsg' => __( 'No post types selected.', 'wordai-pro' ) ] );
        }

        // Ensure sources table is in sync with live WP posts
        self::sync_sources( $post_types );
        self::ensure_default_selection_for_types( $post_types );

        // Embedding model from UI (OpenAI or Gemini list)
        $model = sanitize_text_field( $_POST['embedding_model'] ?? '' );
        if ( $model === '' ) {
            // Fallback: agent’s default model
            if ( defined( "$agent_class::DEFAULT_EMBEDDING_MODEL" ) ) {
                $model = constant( "$agent_class::DEFAULT_EMBEDDING_MODEL" );
            } else {
                $model = 'text-embedding-3-small';
            }
        }

        $chunk_size = intval( $_POST['chunk_size'] ?? 500 );

        // 🔍 Provider-aware pending rows (Option B)
        $all_pending_rows = self::get_pending_sources_for_provider( $post_types, $provider, 0 );
        $pending_rows     = self::apply_embedding_item_limit_to_rows( $all_pending_rows, $provider );
        $total_items      = count( $pending_rows );

        if ( $total_items === 0 ) {
            $limit_message = count( $all_pending_rows ) > 0 ? self::get_embedding_limit_message() : '';
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => $limit_message ? $limit_message : __( 'No new or modified content needs indexing for this provider.', 'wordai-pro' ),
            ] );
        }

        // Count total words across all pending posts
        $total_words = 0;
        foreach ( $pending_rows as $row ) {
            $content = wp_strip_all_tags(
                get_post_field( 'post_content', $row->source_id ) . ' ' .
                get_post_field( 'post_title',   $row->source_id )
            );
            $total_words += str_word_count( $content );
        }

        if ( $total_words <= 0 ) {
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => __( 'No usable content found for indexing.', 'wordai-pro' ),
            ] );
        }

        // 👉 Delegate token & cost computation to provider-specific agent
        $estimate = call_user_func( [ $agent_class, 'estimate_cost' ], $total_words, $model );

        if ( ! is_array( $estimate ) || ( $estimate['status'] ?? 'fail' ) !== 'success' ) {
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => $estimate['feedbackMsg'] ?? __( 'Cost estimation failed.', 'wordai-pro' ),
            ] );
        }

        // Normalize outputs for JS
        $total_tokens   = (int) ( $estimate['total_tokens']   ?? 0 );
        $estimated_cost = (string)( $estimate['estimated_cost'] ?? '$0.00' );

        // Simple duration estimate (provider-neutral)
        $duration_est = round( ( $total_tokens / 1500 ) / 60, 2 );

        wp_send_json( [
            'status'         => 'success',
            'feedbackMsg'    => sprintf(
                __( 'Estimated cost for %d item(s) needing indexing for this provider.', 'wordai-pro' ),
                $total_items
            ),
            'total_tokens'   => number_format_i18n( $total_tokens ),
            'estimated_cost' => $estimated_cost,
            'duration_est'   => $duration_est,
            'embedding_model'=> $model,
            'model'          => $model,
            'chunk_size'     => $chunk_size,
            'provider'       => $provider,
        ] );
    }

    /**
     * ===========================================================
     * Start Indexing (Launcher) — Provider-aware
     * -----------------------------------------------------------
     * - Syncs sources for selected post types
     * - Determines pending items for CURRENT provider (Option B)
     * - Initializes progress table
     * - Schedules background run via WP-Cron
     * - Fallback: runs inline if cron cannot schedule
     * ===========================================================
     */
    public static function start_indexing() {
        check_ajax_referer( 'scwordai_wpnonce', 'security' );

        if ( ! current_user_can( 'manage_options' ) ) {
            error_log( '[WordAI Agent] ❌ Permission denied during start_indexing().' );
            if ( class_exists( 'SftcyWordaiSupport' ) ) {
                SftcyWordaiSupport::log_event( 'embedding', 'error', 'agent_start_permission_denied', 'Permission denied during AI Agent indexing start.', array(), self::get_current_provider_slug(), 'agent', 0 );
            }
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => __( 'Permission denied.', 'wordai-pro' ),
            ] );
        }

        $agent_class = self::get_current_agent_class();
        if ( ! $agent_class ) {
            if ( class_exists( 'SftcyWordaiSupport' ) ) {
                SftcyWordaiSupport::log_event( 'embedding', 'error', 'agent_class_missing', 'No valid embedding agent class found for the current provider.', array(), self::get_current_provider_slug(), 'agent', 0 );
            }
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => __( 'No valid embedding agent class found for the current provider.', 'wordai-pro' ),
            ] );
        }

        $provider = self::get_current_provider_slug();

        global $wpdb;
        $tbl_sources = $wpdb->prefix . 'wordai_agent_sources';

        // Selected post types from UI
        $post_types = isset( $_POST['post_types'] ) && is_array( $_POST['post_types'] )
            ? array_map( 'sanitize_text_field', $_POST['post_types'] )
            : [ 'post' ];

        if ( empty( $post_types ) ) {
            error_log( '[WordAI Agent] ⚠ No post types selected for indexing.' );
            if ( class_exists( 'SftcyWordaiSupport' ) ) {
                SftcyWordaiSupport::log_event( 'embedding', 'warning', 'agent_no_post_types', 'AI Agent indexing was started without any selected post types.', array(), $provider, 'agent', 0 );
            }
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => __( 'No post types selected.', 'wordai-pro' ),
            ] );
        }

        // Embedding model from UI
        $model = sanitize_text_field( $_POST['embedding_model'] ?? '' );
        if ( $model === '' ) {
            if ( defined( "$agent_class::DEFAULT_EMBEDDING_MODEL" ) ) {
                $model = constant( "$agent_class::DEFAULT_EMBEDDING_MODEL" );
            } else {
                $model = 'text-embedding-3-small';
            }
        }

        // Persist last-used embedding model for background runs        
        update_option( 'sc_wordai_agent_last_embedding_model_' . $provider, $model );

        // Optional verbose logging toggle
        $enable_logging = ! empty( $_POST['enable_logging'] );
        update_option( 'sftcy_wordai_debug_enabled', $enable_logging ? 1 : 0 );

        if ( $enable_logging ) {
            error_log( '[WordAI Agent] ▶ Start Indexing triggered for: ' . implode( ', ', $post_types ) );
            error_log( '[WordAI Agent] ▶ Using provider: ' . $provider . ' | embedding model: ' . $model );
        }

        // Sync sources against live WP posts
        self::sync_sources( $post_types );
        self::ensure_default_selection_for_types( $post_types );

        // Reset any lingering "indexing" rows back to "ready"
        $wpdb->query( "UPDATE {$tbl_sources} SET status='ready' WHERE status='indexing'" );

        // Provider-aware pending rows (used just for total count here)
        $all_pending_rows = self::get_pending_sources_for_provider( $post_types, $provider, 0 );
        $pending_rows     = self::apply_embedding_item_limit_to_rows( $all_pending_rows, $provider );
        $ready_count      = count( $pending_rows );

        if ( $ready_count === 0 && count( $all_pending_rows ) > 0 ) {
            self::update_progress( 0, 0, 'idle' );
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => self::get_embedding_limit_message(),
            ] );
        }

        if ( $enable_logging ) {
            error_log( "[WordAI Agent] Found {$ready_count} item(s) pending for provider '{$provider}'." );
        }

        // Initialize progress table
        update_option( 'sc_wordai_agent_cancel_requested', false );
        self::update_progress( 0, $ready_count, $ready_count > 0 ? 'running' : 'idle' );

        // Schedule background event (with fallback)
        $timestamp  = time() + 2;
        $event_args = [
            'post_types'      => $post_types,
            'embedding_model' => $model,
            // Provider is derived at runtime from SftcyWordaiBase, but
            // you could also pass it explicitly if you prefer.
        ];

        // Clear any previously pending runs
        wp_clear_scheduled_hook( 'sc_wordai_agent_run_indexing_event' );

        $scheduled = wp_schedule_single_event( $timestamp, 'sc_wordai_agent_run_indexing_event', [ $event_args ] );

        if ( $enable_logging ) {
            error_log( '[WordAI Agent] wp_schedule_single_event result: ' . var_export( $scheduled, true ) );
        }

        // Fallback for local (cron disabled)
        if ( ! $scheduled || ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) ) {
            if ( $enable_logging ) {
                error_log( '[WordAI Agent] ⚙ WP-Cron disabled or failed — running inline fallback.' );
            }
            self::run_indexing( $event_args );
        }

        wp_send_json( [
            'status'      => 'success',
            'feedbackMsg' => $ready_count > 0
                ? sprintf(
                    __( 'Indexing started for %d item(s) for this provider.', 'wordai-pro' ),
                    $ready_count
                )
                : __( 'No new or modified items need indexing for this provider.', 'wordai-pro' ),
            'job_status'  => $ready_count > 0 ? 'running' : 'idle',
        ] );
    }


    /**
     * Provider-aware failure storage (no DB columns needed)
     * Stores last 50 failed items per provider in wp_options.
     */
    private static function log_provider_failure( $provider, $source_id, $source_type, $msg ) {
        $provider = sanitize_key( $provider ?: 'openai' );
        $key      = 'sc_wordai_agent_failed_items_' . $provider;

        $items = (array) get_option( $key, [] );

        // store/update keyed by post id (dedupe)
        $source_id = (int) $source_id;
        unset( $items[ $source_id ] ); // ✅ ensures it becomes "most recent" in insertion order
        $items[ $source_id ] = [
            'post_id'     => $source_id,
            'source_type' => sanitize_text_field( $source_type ?: '' ),
            'error_msg'   => sanitize_textarea_field( $msg ?: '' ),
            'time'        => current_time( 'mysql' ),
        ];

        // keep only last 50 (by insertion order)
        if ( count( $items ) > 50 ) {
            $items = array_slice( $items, -50, 50, true );
        }

        update_option( $key, $items, false );
    }

    /**
     * Provider-aware remove failure record when an item succeeds (optional but recommended)
     */
    private static function clear_provider_failure( $provider, $source_id ) {
        $provider = sanitize_key( $provider ?: 'openai' );
        $key      = 'sc_wordai_agent_failed_items_' . $provider;

        $items = (array) get_option( $key, [] );
        if ( isset( $items[ (int) $source_id ] ) ) {
            unset( $items[ (int) $source_id ] );
            update_option( $key, $items, false );
        }
    }



    /**
     * ===========================================================
     * Run Indexing (Background or direct) — Provider-aware
     * -----------------------------------------------------------
     * - Processes queued sources and updates progress table
     * - Uses provider-specific embedding agent (OpenAI or Gemini)
     * - Option B behaviour:
     *      • Each provider embeds each post at least once
     *      • Re-embeds only when text changed or status failed/cancelled
     *
     * @param array $args Optional array from WP-Cron:
     *                    [ 'post_types' => [...], 'embedding_model' => '...' ]
     * ===========================================================
     */
    public static function run_indexing( $args = [] ) {
        $debug_enabled = (bool) get_option( 'sftcy_wordai_debug_enabled' );
        if ( $debug_enabled ) error_log( '[WordAI Agent] 🏁 run_indexing() fired.' );

        $agent_class = self::get_current_agent_class();
        if ( ! $agent_class ) {
            if ( $debug_enabled ) error_log( '[WordAI Agent] ❌ No embedding agent class found.' );
            return;
        }

        $provider = self::get_current_provider_slug();

        // Args may come from WP-Cron or from AJAX
        $post_types = [];
        $model      = '';

        if ( is_array( $args ) ) {
            if ( isset( $args['post_types'] ) && is_array( $args['post_types'] ) ) {
                $post_types = array_map( 'sanitize_text_field', $args['post_types'] );
            }
            if ( ! empty( $args['embedding_model'] ) ) {
                $model = sanitize_text_field( $args['embedding_model'] );
            }
        }

        // Fallback: try POST (if ever called directly via AJAX)
        if ( empty( $post_types ) && isset( $_POST['post_types'] ) ) {
            $post_types = array_map( 'sanitize_text_field', (array) $_POST['post_types'] );
        }
        if ( empty( $post_types ) ) {
            $post_types = [ 'post' ];
        }

        if ( $model === '' ) {
            // Try last used option            
            $model = get_option( 'sc_wordai_agent_last_embedding_model_' . $provider, '' );
        }
        if ( $model === '' && defined( "$agent_class::DEFAULT_EMBEDDING_MODEL" ) ) {
            $model = constant( "$agent_class::DEFAULT_EMBEDDING_MODEL" );
        }
        if ( $model === '' ) {
            $model = 'text-embedding-3-small';
        }

        global $wpdb;
        $tbl_sources = $wpdb->prefix . 'wordai_agent_sources';
        $tbl_embeds  = $wpdb->prefix . 'wordai_agent_embeddings';

        if ( $debug_enabled ) {
            error_log( '[WordAI Agent] Running for types: ' . implode( ', ', $post_types ) );
            error_log( '[WordAI Agent] Provider: ' . $provider . ' | Embedding model: ' . $model );
        }

        self::sync_sources( $post_types );
        self::ensure_default_selection_for_types( $post_types );

        // Provider-aware pending rows (Option B) with batch limit
        $all_pending = self::get_pending_sources_for_provider( $post_types, $provider, 2000 );
        $pending     = self::apply_embedding_item_limit_to_rows( $all_pending, $provider );
        $total       = count( $pending );

        if ( $debug_enabled ) error_log( "[WordAI Agent] Pending count for provider '{$provider}': {$total}" );

        if ( $total === 0 ) {
            self::update_progress( 0, 0, 'idle' );
            if ( $debug_enabled ) error_log( '[WordAI Agent] Nothing to process — exiting.' );
            return;
        }

        $processed   = 0;
        $failed      = 0;
        $start_time  = microtime( true );
        $chunk_size  = 500; // you can later make this dynamic if needed

        self::update_progress( 0, $total, 'running' );

        foreach ( $pending as $row ) {
            if ( get_option( 'sc_wordai_agent_cancel_requested' ) ) {
                self::update_progress( $processed, $total, 'cancelled' );
                update_option( 'sc_wordai_agent_last_run_status', 'cancelled' );
                update_option( 'sc_wordai_agent_last_run_status_' . $provider, 'cancelled' );                
                if ( $debug_enabled ) error_log( '[WordAI Agent] ❌ Cancel requested, stopping job.' );

                update_option( 'sc_wordai_agent_last_cancelled_remaining_' . $provider, max( 0, (int) ( $total - $processed ) ) );
                update_option( 'sc_wordai_agent_last_cancelled_at_' . $provider, current_time( 'mysql' ) );

                return;
            }

            // Mark as indexing for global view (not provider-specific, but ok)
            $wpdb->update(
                $tbl_sources,
                [ 'status' => 'indexing' ],
                [ 'id'     => $row->id ],
                [ '%s' ],
                [ '%d' ]
            );

            $content = wp_strip_all_tags(
                get_post_field( 'post_title', $row->source_id ) . "\n\n" .
                get_post_field( 'post_content', $row->source_id )
            );
            if ( empty( trim( $content ) ) ) {
                $wpdb->update(
                    $tbl_sources,
                    [
                        'status'    => 'failed',
                        'error_msg' => 'Empty content.',
                    ],
                    [ 'id' => $row->id ],
                    [ '%s', '%s' ],
                    [ '%d' ]
                );

                // provider-aware failed list
                self::log_provider_failure( $provider, $row->source_id, $row->source_type, 'Empty content.' );
                if ( class_exists( 'SftcyWordaiSupport' ) ) {
                    SftcyWordaiSupport::log_event( 'embedding', 'error', 'agent_empty_content', 'AI Agent indexing failed because the source content was empty.', array( 'source_type' => $row->source_type ), $provider, 'source', (int) $row->source_id );
                }

                $failed++;
                $processed++;
                self::update_progress( $processed, $total, 'running' );
                continue;
            }

            // 💥 IMPORTANT: remove old embeddings for this provider+post
            // so we don't accumulate stale vectors when content changed.
            $wpdb->delete(
                $tbl_embeds,
                [
                    'source_id' => (int) $row->source_id,
                    'provider'  => $provider,
                ],
                [ '%d', '%s' ]
            );

            // Use provider-specific chunking (OpenAI/Gemini)
            $chunks = call_user_func( [ $agent_class, 'chunk_text' ], $content, $chunk_size );
            if ( ! is_array( $chunks ) || empty( $chunks ) ) {
                $chunks = array_filter( array( trim( $content ) ) );
            }

            try {
                foreach ( $chunks as $chunk ) {
                    // Provider-specific embedding call
                    $resp = call_user_func(
                        [ $agent_class, 'create_embeddings' ],
                        [ $chunk ],
                        $model
                    );

                    if ( ! is_array( $resp ) || ( $resp['status'] ?? 'fail' ) !== 'success' || empty( $resp['embeddings'][0] ) ) {
                        $msg = $resp['feedbackMsg'] ?? 'Embedding request failed.';
                        throw new Exception( $msg );
                    }

                    // Provider-specific storage (shared table, provider+model tracked)
                    call_user_func(
                        [ $agent_class, 'store_embedding' ],
                        $row->source_id,
                        $chunk,
                        $resp['embeddings'][0],
                        $model
                    );
                }

                // Success for this source (global view)
                $wpdb->update(
                    $tbl_sources,
                    [
                        'status'       => 'done',
                        'last_indexed' => current_time( 'mysql' ),
                        'error_msg'    => null,
                    ],
                    [ 'id' => $row->id ],
                    [ '%s', '%s', '%s' ],
                    [ '%d' ]
                );

                // Clear from provider-aware failed list if it was previously failed
                self::clear_provider_failure( $provider, $row->source_id );

            } catch ( Exception $e ) {
                $wpdb->update(
                    $tbl_sources,
                    [
                        'status'       => 'failed',
                        'error_msg'    => $e->getMessage(),
                    ],
                    [ 'id' => $row->id ],
                    [ '%s', '%s' ],
                    [ '%d' ]
                );

                // provider-aware failed list
                self::log_provider_failure( $provider, $row->source_id, $row->source_type, $e->getMessage() );
                if ( class_exists( 'SftcyWordaiSupport' ) ) {
                    SftcyWordaiSupport::log_event( 'embedding', 'error', 'agent_embedding_failed', $e->getMessage(), array( 'source_type' => $row->source_type, 'model' => $model ), $provider, 'source', (int) $row->source_id );
                }

                $failed++;
            }

            $processed++;
            if ( $processed % 3 === 0 || $processed === $total ) {
                if ( $debug_enabled ) error_log( "[WordAI Agent] ✅ Progress: {$processed}/{$total}" );
            }

            self::update_progress( $processed, $total, 'running' );
        }

        $duration = round( microtime( true ) - $start_time, 2 );
        self::update_progress( $processed, $total, 'completed' );

        // legacy (keep)
        update_option( 'sc_wordai_agent_last_run_status', 'completed' );
        update_option( 'sc_wordai_agent_last_run', current_time( 'mysql' ) );

        // ✅ provider-aware
        update_option( 'sc_wordai_agent_last_run_status_' . $provider, 'completed' );
        update_option( 'sc_wordai_agent_last_run_' . $provider, current_time( 'mysql' ) );
        update_option( 'sc_wordai_agent_last_cancelled_remaining_' . $provider, 0 );

        if ( $debug_enabled ) {
            error_log( "[WordAI Agent] 🎉 Completed: {$processed} done, {$failed} failed in {$duration}s." );
            error_log( '[WordAI Agent] 🔕 Verbose logging auto-disabled.' );
        }

        // Automatically disable verbose mode at the end
        delete_option( 'sftcy_wordai_debug_enabled' );
    }

    /**
     * ----------------------------------------------------------
     *  Update progress table (single row upsert)
     *  - processed: number of attempted items (done + failed)
     *  - total:     total items scheduled for this run
     *  - status:    running | completed | cancelled | idle
     * ----------------------------------------------------------
     */
    private static function update_progress( $processed, $total, $status ) {
        global $wpdb;
        $tbl = $wpdb->prefix . 'wordai_agent_progress';
        $wpdb->replace(
            $tbl,
            [
                'id'          => 1,
                'total_items' => max( 0, (int) $total ),
                'processed'   => max( 0, (int) $processed ),
                'status'      => sanitize_text_field( $status ),
                'updated_at'  => current_time( 'mysql' ),
            ],
            [ '%d', '%d', '%d', '%s', '%s' ]
        );
    }

    /**
     * ----------------------------------------------------------
     *  Legacy/basic progress endpoint (kept for compatibility)
     * ----------------------------------------------------------
     */
    public static function get_index_progress() {
        check_ajax_referer( 'scwordai_wpnonce', 'security' );

        global $wpdb;
        $tbl = $wpdb->prefix . 'wordai_agent_progress';
        $row = $wpdb->get_row( "SELECT * FROM $tbl WHERE id = 1" );

        if ( ! $row ) {
            wp_send_json( [ 'status' => 'fail', 'feedbackMsg' => __( 'No progress record found.', 'wordai-pro' ) ] );
        }

        $percent = ( $row->total_items > 0 )
            ? round( ( $row->processed / $row->total_items ) * 100, 2 )
            : 0;

        wp_send_json( [
            'status'     => 'success',
            'processed'  => intval( $row->processed ),
            'total'      => intval( $row->total_items ),
            'percent'    => $percent,
            'job_status' => $row->status,
        ] );
    }

    /**
     * ----------------------------------------------------------
     * Cancel Indexing
     * ----------------------------------------------------------
     */
    public static function cancel_indexing() {
        check_ajax_referer( 'scwordai_wpnonce', 'security' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => __( 'Permission denied.', 'wordai-pro' ),
            ] );
        }

        update_option( 'sc_wordai_agent_cancel_requested', true );

        global $wpdb;
        $tbl_sources   = $wpdb->prefix . 'wordai_agent_sources';
        $tbl_progress  = $wpdb->prefix . 'wordai_agent_progress';

        // Reset any in-flight rows to 'ready' so a re-run can resume cleanly
        $wpdb->query( "UPDATE {$tbl_sources} SET status = 'ready' WHERE status = 'indexing'" );

        // Flip progress row so poller instantly shows "cancelled"
        $wpdb->update(
            $tbl_progress,
            [ 'status' => 'cancelled', 'updated_at' => current_time( 'mysql' ) ],
            [ 'id' => 1 ],
            [ '%s', '%s' ],
            [ '%d' ]
        );

        wp_send_json( [
            'status'      => 'success',
            'feedbackMsg' => __( 'Indexing cancellation requested. The process will stop shortly.', 'wordai-pro' ),
        ] );
    }

    /**
     * ===========================================================
     * Get Indexing Progress (AJAX)
     * -----------------------------------------------------------
     * - Returns current indexing percentage, status, and message.
     * - Ensures consistent non-empty feedback for UI display.
     * ===========================================================
     */
    public static function sc_wordai_agent_get_progress() {
        check_ajax_referer( 'scwordai_wpnonce', 'security' );
        global $wpdb;

        $tbl_progress = $wpdb->prefix . 'wordai_agent_progress';
        $tbl_sources  = $wpdb->prefix . 'wordai_agent_sources';

        // Fetch latest progress record (if any)
        $row = $wpdb->get_row( "SELECT * FROM {$tbl_progress} ORDER BY id DESC LIMIT 1" );

        $percent     = 0.0;
        $job_status  = 'idle';
        $feedbackMsg = '';

        if ( $row ) {
            $total      = max( (int) $row->total_items, 1 );
            $processed  = (int) $row->processed;
            $percent    = round( ( $processed / $total ) * 100, 2 );
            $job_status = $row->status;

            switch ( $job_status ) {
                case 'running':
                    $feedbackMsg = sprintf(
                        __( 'Processing %d of %d items...', 'wordai-pro' ),
                        $processed,
                        $total
                    );
                    break;

                case 'completed':
                    $done = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$tbl_sources} WHERE status='done'" );
                    $fail = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$tbl_sources} WHERE status='failed'" );
                    $feedbackMsg = sprintf(
                        __( 'Indexing completed: %d done, %d failed.', 'wordai-pro' ),
                        $done,
                        $fail
                    );
                    $percent = 100.00;
                    break;

                case 'cancelled':
                    $feedbackMsg = __( 'Indexing cancelled by user.', 'wordai-pro' );
                    break;

                case 'idle':
                default:
                    $ready_count = (int) $wpdb->get_var(
                        "SELECT COUNT(*) FROM {$tbl_sources} WHERE status IN ('ready','indexing')"
                    );
                    $done_count  = (int) $wpdb->get_var(
                        "SELECT COUNT(*) FROM {$tbl_sources} WHERE status='done'"
                    );
                    $fail_count  = (int) $wpdb->get_var(
                        "SELECT COUNT(*) FROM {$tbl_sources} WHERE status='failed'"
                    );

                    if ( $ready_count === 0 && $done_count > 0 ) {
                        $feedbackMsg = sprintf(
                            __( 'All %d valid sources are already indexed.', 'wordai-pro' ),
                            $done_count
                        );
                        $percent = 100.00;
                    } elseif ( $ready_count === 0 && $done_count === 0 && $fail_count > 0 ) {
                        $feedbackMsg = sprintf(
                            __( 'No valid content found for indexing. %d sources failed or were empty.', 'wordai-pro' ),
                            $fail_count
                        );
                        $percent = 100.00;
                    } elseif ( $ready_count === 0 && $done_count === 0 && $fail_count === 0 ) {
                        $feedbackMsg = __( 'No sources available for indexing yet.', 'wordai-pro' );
                        $percent = 0.00;
                    } else {
                        $feedbackMsg = __( 'No active indexing job.', 'wordai-pro' );
                        $percent = 0.00;
                    }
                    break;
            }
        } else {
            $feedbackMsg = __( 'No indexing activity found yet.', 'wordai-pro' );
        }

        if ( empty( trim( $feedbackMsg ) ) ) {
            $feedbackMsg = __( 'No current indexing update available.', 'wordai-pro' );
        }

        wp_send_json( [
            'status'      => 'success',
            'percent'     => $percent,
            'job_status'  => $job_status,
            'feedbackMsg' => $feedbackMsg,
        ] );
    }

    public static function get_source_items() {
        check_ajax_referer( 'scwordai_wpnonce', 'security' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => __( 'Permission denied.', 'wordai-pro' ),
            ] );
        }

        global $wpdb;

        $provider = self::get_current_provider_slug();
        $post_types = isset( $_POST['post_types'] ) && is_array( $_POST['post_types'] )
            ? array_values( array_filter( array_map( 'sanitize_key', $_POST['post_types'] ) ) )
            : array();

        if ( empty( $post_types ) ) {
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => __( 'Select at least one content type to view embeddable items.', 'wordai-pro' ),
                'items'       => array(),
            ] );
        }

        $active_type = isset( $_POST['active_type'] ) ? sanitize_key( $_POST['active_type'] ) : $post_types[0];
        if ( ! in_array( $active_type, $post_types, true ) ) {
            $active_type = $post_types[0];
        }

        $page          = max( 1, (int) ( $_POST['page'] ?? 1 ) );
        $per_page      = min( 50, max( 5, (int) ( $_POST['per_page'] ?? 10 ) ) );
        $offset        = ( $page - 1 ) * $per_page;
        $search        = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : '';
        $status_filter = isset( $_POST['status_filter'] ) ? sanitize_key( $_POST['status_filter'] ) : 'all';

        self::sync_sources( $post_types );
        self::ensure_default_selection_for_types( $post_types );

        $tbl_sources   = $wpdb->prefix . 'wordai_agent_sources';
        $tbl_embeds    = $wpdb->prefix . 'wordai_agent_embeddings';
        $tbl_selection = $wpdb->prefix . 'wordai_agent_source_selection';
        $posts_table   = $wpdb->posts;

        $where  = "WHERE s.source_type = %s AND s.status <> 'archived'";
        $params = array( $provider, $active_type );

        if ( $search !== '' ) {
            $where   .= ' AND (s.title LIKE %s OR p.post_title LIKE %s)';
            $like     = '%' . $wpdb->esc_like( $search ) . '%';
            $params[] = $like;
            $params[] = $like;
        }

        $status_sql = "
            CASE
                WHEN s.status = 'failed' THEN 'failed'
                WHEN p.post_modified > e.last_embed THEN 'needs_reindex'
                WHEN e.last_embed IS NULL THEN 'needs_embedding'
                ELSE 'embedded'
            END
        ";

        if ( in_array( $status_filter, array( 'embedded', 'needs_embedding', 'needs_reindex', 'failed' ), true ) ) {
            $where .= " HAVING item_status = %s";
            $params[] = $status_filter;
        }

        $base_sql = "
            FROM {$tbl_sources} AS s
            INNER JOIN {$posts_table} AS p ON p.ID = s.source_id
            INNER JOIN {$tbl_selection} AS sel ON sel.source_row_id = s.id
            LEFT JOIN (
                SELECT source_id, MAX(created_at) AS last_embed
                FROM {$tbl_embeds}
                WHERE provider = %s
                GROUP BY source_id
            ) AS e ON e.source_id = s.source_id
            {$where}
        ";

        $count_sql = "SELECT COUNT(*) FROM (SELECT s.id, {$status_sql} AS item_status {$base_sql}) AS counted";
        $total     = (int) $wpdb->get_var( $wpdb->prepare( $count_sql, $params ) );

        $list_params = array_merge( $params, array( $per_page, $offset ) );
        $items_sql = "
            SELECT
                s.id AS source_row_id,
                s.source_type,
                s.source_id,
                COALESCE(NULLIF(s.title, ''), p.post_title) AS title,
                p.post_modified AS post_modified_gmt,
                e.last_embed,
                sel.selected,
                {$status_sql} AS item_status
            {$base_sql}
            ORDER BY p.post_modified DESC, s.id DESC
            LIMIT %d OFFSET %d
        ";

        $rows  = $wpdb->get_results( $wpdb->prepare( $items_sql, $list_params ) );
        $items = array();

        if ( is_array( $rows ) ) {
            foreach ( $rows as $row ) {
                $post_type_obj = get_post_type_object( $row->source_type );
                $items[] = array(
                    'source_row_id' => (int) $row->source_row_id,
                    'source_id'     => (int) $row->source_id,
                    'source_type'   => sanitize_key( $row->source_type ),
                    'type_label'    => $post_type_obj ? $post_type_obj->labels->singular_name : $row->source_type,
                    'title'         => $row->title ? html_entity_decode( wp_specialchars_decode( $row->title, ENT_QUOTES ), ENT_QUOTES | ENT_HTML5, 'UTF-8' ) : sprintf( __( 'Untitled #%d', 'wordai-pro' ), (int) $row->source_id ),
                    'edit_url'      => get_edit_post_link( (int) $row->source_id, '' ),
                    'selected'      => (bool) $row->selected,
                    'status'        => $row->item_status,
                    'modified'      => $row->post_modified_gmt,
                    'last_embed'    => $row->last_embed ? $row->last_embed : '',
                );
            }
        }

        $limit          = self::get_embedding_item_limit();
        $selected_count = self::get_selected_source_count();

        wp_send_json( array(
            'status'         => 'success',
            'items'          => $items,
            'total'          => $total,
            'page'           => $page,
            'per_page'       => $per_page,
            'total_pages'    => max( 1, (int) ceil( $total / $per_page ) ),
            'active_type'    => $active_type,
            'provider'       => $provider,
            'selected_count' => $selected_count,
            'limit'          => $limit,
            'limit_reached'  => $limit > 0 && $selected_count >= $limit,
        ) );
    }

    public static function update_source_selection() {
        check_ajax_referer( 'scwordai_wpnonce', 'security' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => __( 'Permission denied.', 'wordai-pro' ),
            ] );
        }

        global $wpdb;

        $source_row_id = isset( $_POST['source_row_id'] ) ? absint( $_POST['source_row_id'] ) : 0;
        $selected      = ! empty( $_POST['selected'] ) ? 1 : 0;

        if ( $source_row_id <= 0 ) {
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => __( 'Invalid content item.', 'wordai-pro' ),
            ] );
        }

        $tbl_sources   = $wpdb->prefix . 'wordai_agent_sources';
        $tbl_selection = $wpdb->prefix . 'wordai_agent_source_selection';

        $source_exists = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$tbl_sources} WHERE id = %d AND status <> 'archived'",
                $source_row_id
            )
        );

        if ( $source_exists <= 0 ) {
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => __( 'This content item is no longer available for embedding.', 'wordai-pro' ),
            ] );
        }

        $limit = self::get_embedding_item_limit();
        if ( $selected && $limit > 0 ) {
            $selected_count = (int) $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT COUNT(*)
                     FROM {$tbl_selection} AS sel
                     INNER JOIN {$tbl_sources} AS s ON s.id = sel.source_row_id
                     WHERE sel.selected = 1
                       AND s.status <> 'archived'
                       AND sel.source_row_id <> %d",
                    $source_row_id
                )
            );

            if ( $selected_count >= $limit ) {
                wp_send_json( [
                    'status'         => 'fail',
                    'feedbackMsg'    => self::get_embedding_limit_message(),
                    'selected_count' => $selected_count,
                    'limit'          => $limit,
                    'limit_reached'  => true,
                ] );
            }
        }

        $existing_id = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT id FROM {$tbl_selection} WHERE source_row_id = %d",
                $source_row_id
            )
        );

        if ( $existing_id > 0 ) {
            $wpdb->update(
                $tbl_selection,
                [
                    'selected'   => $selected,
                    'updated_at' => current_time( 'mysql' ),
                ],
                [ 'id' => $existing_id ],
                [ '%d', '%s' ],
                [ '%d' ]
            );
        } else {
            $wpdb->insert(
                $tbl_selection,
                [
                    'source_row_id' => $source_row_id,
                    'selected'      => $selected,
                    'created_at'    => current_time( 'mysql' ),
                    'updated_at'    => current_time( 'mysql' ),
                ],
                [ '%d', '%d', '%s', '%s' ]
            );
        }

        self::enforce_selection_limit();

        $selected_count = self::get_selected_source_count();

        wp_send_json( [
            'status'         => 'success',
            'feedbackMsg'    => $selected
                ? __( 'Content item selected for embedding.', 'wordai-pro' )
                : __( 'Content item removed from embedding selection.', 'wordai-pro' ),
            'selected_count' => $selected_count,
            'limit'          => $limit,
            'limit_reached'  => $limit > 0 && $selected_count >= $limit,
        ] );
    }

    /**
     * ============================================================
     * Get Indexing Summary (AJAX)
     * ------------------------------------------------------------
     * Returns total counts + tokens + last run timestamp
     * (Note: global across providers; per-provider breakdown can
     *        be added later if needed.)
     * ============================================================
     */
    // public static function get_summary() {
    //     check_ajax_referer( 'scwordai_wpnonce', 'security' );

    //     if ( ! current_user_can( 'manage_options' ) ) {
    //         wp_send_json( [
    //             'status'      => 'fail',
    //             'feedbackMsg' => __( 'Permission denied.', 'wordai-pro' ),
    //         ] );
    //     }

    //     global $wpdb;
    //     $table = $wpdb->prefix . 'wordai_agent_sources';

    //     $total_indexed   = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table} WHERE status = 'done'" );
    //     $total_failed    = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table} WHERE status = 'failed'" );
    //     $total_cancelled = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table} WHERE status = 'cancelled'" );
    //     $total_tokens    = (int) $wpdb->get_var( "SELECT COALESCE(SUM(tokens), 0) FROM {$table} WHERE status = 'done'" );

    //     $last_run = get_option( 'sc_wordai_agent_last_run', __( 'N/A', 'wordai-pro' ) );

    //     if ( $total_indexed === 0 && $total_failed === 0 && $total_cancelled === 0 ) {
    //         $feedback = __( 'No indexed activity found yet.', 'wordai-pro' );
    //     } else {
    //         $feedback = sprintf(
    //             __( 'Summary refreshed — %d indexed, %d failed, %d cancelled.', 'wordai-pro' ),
    //             $total_indexed,
    //             $total_failed,
    //             $total_cancelled
    //         );
    //     }

    //     wp_send_json( [
    //         'status'          => 'success',
    //         'feedbackMsg'     => $feedback,
    //         'total_indexed'   => $total_indexed,
    //         'total_failed'    => $total_failed,
    //         'total_cancelled' => $total_cancelled,
    //         'total_tokens'    => $total_tokens,
    //         'last_run'        => $last_run,
    //     ] );
    // }

    public static function get_summary() {
        check_ajax_referer( 'scwordai_wpnonce', 'security' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json([
                'status' => 'fail',
                'feedbackMsg' => __( 'Permission denied.', 'wordai-pro' ),
            ]);
        }

        global $wpdb;

        $provider   = self::get_current_provider_slug();
        $tbl_embeds = $wpdb->prefix . 'wordai_agent_embeddings';

        // Optional UI filter: post_types[]
        $post_types = isset($_POST['post_types']) && is_array($_POST['post_types'])
            ? array_map('sanitize_text_field', $_POST['post_types'])
            : [];

        // ✅ Ensure sources are synced before pending calc
        if ( ! empty( $post_types ) ) {
            self::sync_sources( $post_types );
            self::ensure_default_selection_for_types( $post_types );
        }

        // ✅ Indexed = distinct source_id in embeddings for provider
        $total_indexed = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(DISTINCT source_id)
                FROM {$tbl_embeds}
                WHERE provider = %s",
                $provider
            )
        );

        // ✅ Failed = provider-aware failure map (your option cache)
        $failed_map   = (array) get_option( 'sc_wordai_agent_failed_items_' . $provider, [] );
        $total_failed = count( $failed_map );

        // ✅ Cancelled = provider-aware remaining count
        $total_cancelled = (int) get_option( 'sc_wordai_agent_last_cancelled_remaining_' . $provider, 0 );

        // ✅ Pending = Option B pending resolver (only meaningful if UI sends post_types)
        $pending_total = 0;
        if ( ! empty( $post_types ) ) {
            $pending_rows  = self::get_pending_sources_for_provider( $post_types, $provider, 0 );
            $pending_total = is_array($pending_rows) ? count($pending_rows) : 0;
        }

        // ✅ Provider-aware last run
        $last_run = get_option( 'sc_wordai_agent_last_run_' . $provider, '' );
        if ( $last_run === '' ) {
            $last_run = get_option( 'sc_wordai_agent_last_run', __( 'N/A', 'wordai-pro' ) );
        }

        $feedback = sprintf(
            __( 'Summary refreshed for %s — %d indexed, %d failed, %d cancelled.', 'wordai-pro' ),
            $provider,
            $total_indexed,
            $total_failed,
            $total_cancelled
        );

        wp_send_json([
            'status'          => 'success',
            'feedbackMsg'     => $feedback,
            'provider'        => $provider,
            'total_indexed'   => $total_indexed,
            'total_failed'    => $total_failed,
            'total_cancelled' => $total_cancelled,
            'pending_total'   => $pending_total,
            'total_tokens'    => 0,
            'last_run'        => $last_run,
        ]);
    }


    /**
     * =======================================================================
     * Sync sources table with current WordPress content
     * -----------------------------------------------------------------------
     * - Keeps `wp_wordai_agent_sources` aligned with live WP data (per post).
     * - Provider-agnostic: we use provider separation only in embeddings
     *   table, not here.
     * - Adds new published posts as `ready`.
     * - If post modified since last_indexed → mark `ready` to re-index.
     * - Archives deleted posts (status='archived').
     * - Used automatically before Estimate Cost & Start Indexing.
     * =======================================================================
     *
     * @param array $post_types
     * @return array Summary [inserted, updated, archived, scanned].
     */
    private static function sync_sources( $post_types = [] ) {
        global $wpdb;

        if ( empty( $post_types ) ) {
            return [
                'inserted' => 0,
                'updated'  => 0,
                'archived' => 0,
                'scanned'  => 0,
            ];
        }

        $sources_table = $wpdb->prefix . 'wordai_agent_sources';
        $inserted = $updated = $archived = $scanned = 0;

        foreach ( $post_types as $pt ) {

            // 1️⃣ Fetch all current published posts for this post type
            $wp_posts = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT ID, post_modified
                     FROM {$wpdb->posts}
                     WHERE post_type = %s AND post_status = 'publish'",
                    $pt
                ),
                ARRAY_A
            );
            $scanned += count( $wp_posts );

            if ( empty( $wp_posts ) ) {
                continue;
            }

            $wp_ids    = array_column( $wp_posts, 'ID' );
            $wp_mod_map = [];
            foreach ( $wp_posts as $p ) {
                $wp_mod_map[ (int) $p['ID'] ] = $p['post_modified'];
            }

            // 2️⃣ Fetch existing records for this type in one call
            $existing_rows = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT id, source_id, status, last_indexed
                     FROM {$sources_table}
                     WHERE source_type = %s",
                    $pt
                ),
                OBJECT_K
            );

            $existing_map = [];
            foreach ( $existing_rows as $row ) {
                $existing_map[ (int) $row->source_id ] = $row;
            }

            // 3️⃣ INSERT new posts that don’t exist yet
            $new_ids = array_diff( $wp_ids, array_keys( $existing_map ) );
            if ( ! empty( $new_ids ) ) {
                foreach ( $new_ids as $pid ) {
                    $wpdb->insert(
                        $sources_table,
                        [
                            'source_type'  => $pt,
                            'source_id'    => (int) $pid,
                            'url'          => get_permalink( $pid ),
                            'title'        => get_the_title( $pid ),
                            'status'       => 'ready',
                            'tokens'       => 0,
                            'last_indexed' => null,
                            'created_at'   => current_time( 'mysql' ),
                            'updated_at'   => current_time( 'mysql' ),
                        ],
                        [ '%s', '%d', '%s', '%s', '%s', '%d', '%s', '%s' ]
                    );
                }
                $inserted += count( $new_ids );
            }

            // 4️⃣ UPDATE existing posts that changed since last_indexed
            $to_update_ids = [];
            foreach ( $wp_ids as $pid ) {
                $row = $existing_map[ $pid ] ?? null;
                if ( ! $row ) {
                    continue;
                }

                $modified_gmt = $wp_mod_map[ $pid ] ?? null;
                $last_indexed = $row->last_indexed;

                $needs_update = false;

                if ( empty( $last_indexed ) ) {
                    $needs_update = true;
                } elseif ( $modified_gmt && strtotime( $modified_gmt ) > strtotime( $last_indexed ) ) {
                    $needs_update = true;
                }

                if ( $needs_update && $row->status !== 'indexing' ) {
                    $to_update_ids[] = $pid;
                }
            }

            if ( ! empty( $to_update_ids ) ) {
                $ids_sql = implode( ',', array_map( 'intval', $to_update_ids ) );
                $wpdb->query(
                    "UPDATE {$sources_table}
                     SET status='ready', tokens=0, updated_at=NOW()
                     WHERE source_type='{$pt}' AND source_id IN ({$ids_sql})"
                );
                $updated += count( $to_update_ids );
            }

            // 5️⃣ ARCHIVE rows that no longer exist in WordPress
            $existing_ids = array_keys( $existing_map );
            $deleted_ids  = array_diff( $existing_ids, $wp_ids );
            if ( ! empty( $deleted_ids ) ) {
                $in = implode( ',', array_map( 'intval', $deleted_ids ) );
                $wpdb->query(
                    "UPDATE {$sources_table}
                     SET status='archived', updated_at=NOW()
                     WHERE source_type='{$pt}' AND source_id IN ({$in})"
                );
                $archived += count( $deleted_ids );
            }
        }

        return [
            'inserted' => $inserted,
            'updated'  => $updated,
            'archived' => $archived,
            'scanned'  => $scanned,
        ];
    }

    /**
     * ============================================================
     * Get Failed Items (for modal display in AI Agent Settings)
     * ------------------------------------------------------------
     * Returns up to 50 latest failed indexing items with:
     *  - Post ID
     *  - Source Type (post/page/product/CPT)
     *  - Title + Edit Link (if exists)
     *  - Error Message
     * ============================================================
     */
    // public static function get_failed_items() {
    //     check_ajax_referer( 'scwordai_wpnonce', 'security' );
    //     global $wpdb;
    //     $tbl = $wpdb->prefix . 'wordai_agent_sources';

    //     $items = $wpdb->get_results("
    //         SELECT id, source_type, source_id, error_msg
    //         FROM {$tbl}
    //         WHERE status = 'failed'
    //         ORDER BY id DESC
    //         LIMIT 50
    //     ");

    //     $result = [];
    //     foreach ( $items as $row ) {
    //         $post_title = get_the_title( $row->source_id );
    //         $edit_link  = get_edit_post_link( $row->source_id, '' );
    //         $result[] = [
    //             'post_id'     => intval( $row->source_id ),
    //             'source_type' => $row->source_type ?: 'Unknown',
    //             'post_title'  => $post_title ?: __( '(Untitled)', 'wordai-pro' ),
    //             'edit_url'    => $edit_link,
    //             'error_msg'   => $row->error_msg,
    //         ];
    //     }

    //     wp_send_json( [
    //         'status' => 'success',
    //         'items'  => $result,
    //     ] );
    // }

    public static function get_failed_items() {
        check_ajax_referer( 'scwordai_wpnonce', 'security' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json( [
                'status'      => 'fail',
                'feedbackMsg' => __( 'Permission denied.', 'wordai-pro' ),
                'items'       => [],
            ] );
        }

        $provider = self::get_current_provider_slug();
        $key      = 'sc_wordai_agent_failed_items_' . $provider;

        $items = (array) get_option( $key, [] );

        // Convert map → list, newest first
        $items_list = array_reverse( array_values( $items ) );

        $result = [];
        foreach ( array_slice( $items_list, 0, 50 ) as $r ) {
            $post_id    = (int) ( $r['post_id'] ?? 0 );
            $post_title = $post_id ? get_the_title( $post_id ) : '';
            $edit_link  = $post_id ? get_edit_post_link( $post_id, '' ) : '';

            $result[] = [
                'post_id'     => $post_id,
                'source_type' => $r['source_type'] ?? 'Unknown',
                'post_title'  => $post_title ?: __( '(Untitled)', 'wordai-pro' ),
                'edit_url'    => $edit_link,
                'error_msg'   => $r['error_msg'] ?? '',
                'time'        => $r['time'] ?? '',
                'provider'    => $provider,
            ];
        }

        wp_send_json( [
            'status'   => 'success',
            'provider' => $provider,
            'items'    => $result,
        ] );
    }


    /**
     * ============================================================
     * Save Chat Settings (AJAX Handler)
     * ------------------------------------------------------------
     * - Saves common chat settings
     * - Saves ONLY active provider's model & fallback message
     * - Preserves inactive provider data (Option B behavior)
     * ============================================================
     */
    public static function sc_wordai_agent_save_chat_settings() {

        check_ajax_referer( 'scwordai_wpnonce', 'security' );

        // 🔐 Permission check
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json( [
                'status'      => 'error',
                'feedbackMsg' => __( 'Permission denied.', 'wordai-pro' ),
            ] );
        }

        $data     = $_POST ?? [];
        $defaults = SftcyWordaiFrontendAgentChat::wordai_chat_default_settings();

        // Load current settings (important for Option B)
        $current  = (array) get_option( 'sc_wordai_agent_chat_settings', [] );
        $current  = wp_parse_args( $current, $defaults );

        /**
         * ------------------------------------------------------------
         * Detect active provider
         * ------------------------------------------------------------
         * - Sent from JS as `provider`
         * - Fallback to global provider if missing
         */
        $provider = sanitize_key( $data['provider'] ?? ( $data['active_provider'] ?? '' ) );
        if ( ! in_array( $provider, [ 'openai', 'google', 'anthropic', 'xai' ], true ) ) {
            $provider = class_exists( 'SftcyWordaiBase' )
                ? SftcyWordaiBase::sc_wordai_get_current_ai_provider()
                : 'openai';
        }

        /**
         * ------------------------------------------------------------
         * Start from CURRENT settings
         * ------------------------------------------------------------
         * This prevents overwriting inactive provider values
         */
        $settings = $current;

        /**
         * ------------------------------------------------------------
         * Common settings (provider-agnostic)
         * ------------------------------------------------------------
         */
        $settings['enabled']         = isset( $data['enabled'] ) ? intval( $data['enabled'] ) : 1;
        $settings['assistant_name']  = sanitize_text_field( $data['assistant_name'] ?? $defaults['assistant_name'] );
        $settings['welcome_message'] = sanitize_textarea_field( $data['welcome_message'] ?? $defaults['welcome_message'] );
        $settings['bubble_label']    = sanitize_text_field( $data['bubble_label'] ?? $defaults['bubble_label'] );
        $settings['theme']           = in_array( $data['theme'] ?? 'light', [ 'light', 'dark', 'auto' ], true )
            ? $data['theme']
            : 'light';
        $settings['bubble_color']    = sanitize_hex_color( $data['bubble_color'] ?? $defaults['bubble_color'] );

        $settings['avatar_style']    = in_array(
            $data['avatar_style'] ?? 'none',
            [ 'none', 'male', 'female', 'bot' ],
            true
        ) ? $data['avatar_style'] : 'none';

        $settings['avatar_color']    = sanitize_hex_color( $data['avatar_color'] ?? $defaults['avatar_color'] );
        $settings['chat_font_size']  = max( 13, min( 18, absint( $data['chat_font_size'] ?? $defaults['chat_font_size'] ) ) );

        $settings['temperature']     = max(
            0.0,
            min( 1.0, floatval( $data['temperature'] ?? $defaults['temperature'] ) )
        );

        $settings['auto_display']    = isset( $data['auto_display'] ) ? intval( $data['auto_display'] ) : 1;

        $settings['position']        = in_array(
            $data['position'] ?? 'bottom-right',
            [ 'bottom-right', 'bottom-left' ],
            true
        ) ? $data['position'] : 'bottom-right';

        $settings['reply_mode']      = 'nonstream';

        $settings['visibility']      = in_array(
            $data['visibility'] ?? 'public',
            [ 'public', 'loggedin' ],
            true
        ) ? $data['visibility'] : 'public';

        $settings['prompt_prefix']   = wp_kses_post( $data['prompt_prefix'] ?? $defaults['prompt_prefix'] );
        $is_basic_tier               = class_exists( 'SftcyWordaiFeatures' ) && SftcyWordaiFeatures::wordai_is_basic_tier();
        $settings['analytics_enabled']     = isset( $data['analytics_enabled'] ) ? intval( $data['analytics_enabled'] ) : 1;
        $settings['feedback_enabled']      = isset( $data['feedback_enabled'] ) ? intval( $data['feedback_enabled'] ) : 1;
        $settings['show_source_citations'] = isset( $data['show_source_citations'] ) ? intval( $data['show_source_citations'] ) : 1;
        $settings['retention_days']        = $is_basic_tier ? 7 : max( 7, min( 365, absint( $data['retention_days'] ?? $defaults['retention_days'] ) ) );
        $settings['handoff_enabled']       = $is_basic_tier ? 0 : ( isset( $data['handoff_enabled'] ) ? intval( $data['handoff_enabled'] ) : 0 );
        $settings['handoff_email']         = sanitize_email( $data['handoff_email'] ?? $defaults['handoff_email'] );
        $settings['handoff_label']         = sanitize_text_field( $data['handoff_label'] ?? $defaults['handoff_label'] );

        $lead_modes = array( 'off', 'optional_before_chat', 'required_before_chat', 'after_first_reply', 'fallback_handoff' );
        $lead_mode  = sanitize_key( $data['lead_capture_mode'] ?? $defaults['lead_capture_mode'] );
        if ( ! in_array( $lead_mode, $lead_modes, true ) ) {
            $lead_mode = 'off';
        }

        $settings['lead_capture_mode']     = $lead_mode;
        $settings['lead_intro_text']       = sanitize_textarea_field( $data['lead_intro_text'] ?? $defaults['lead_intro_text'] );
        $settings['lead_consent_text']     = sanitize_text_field( $data['lead_consent_text'] ?? $defaults['lead_consent_text'] );
        $settings['lead_privacy_url']      = esc_url_raw( $data['lead_privacy_url'] ?? $defaults['lead_privacy_url'] );
        $settings['lead_notify_admin']     = $is_basic_tier ? 0 : ( isset( $data['lead_notify_admin'] ) ? intval( $data['lead_notify_admin'] ) : 0 );
        $settings['lead_name_enabled']     = isset( $data['lead_name_enabled'] ) ? intval( $data['lead_name_enabled'] ) : 1;
        $settings['lead_name_required']    = isset( $data['lead_name_required'] ) ? intval( $data['lead_name_required'] ) : 0;
        $settings['lead_email_enabled']    = $is_basic_tier ? 0 : ( isset( $data['lead_email_enabled'] ) ? intval( $data['lead_email_enabled'] ) : 1 );
        $settings['lead_email_required']   = $is_basic_tier ? 0 : ( isset( $data['lead_email_required'] ) ? intval( $data['lead_email_required'] ) : 0 );
        $settings['lead_phone_enabled']    = $is_basic_tier ? 0 : ( isset( $data['lead_phone_enabled'] ) ? intval( $data['lead_phone_enabled'] ) : 0 );
        $settings['lead_phone_required']   = $is_basic_tier ? 0 : ( isset( $data['lead_phone_required'] ) ? intval( $data['lead_phone_required'] ) : 0 );
        $settings['lead_company_enabled']  = $is_basic_tier ? 0 : ( isset( $data['lead_company_enabled'] ) ? intval( $data['lead_company_enabled'] ) : 0 );
        $settings['lead_company_required'] = $is_basic_tier ? 0 : ( isset( $data['lead_company_required'] ) ? intval( $data['lead_company_required'] ) : 0 );
        $settings['lead_message_enabled']  = $is_basic_tier ? 0 : ( isset( $data['lead_message_enabled'] ) ? intval( $data['lead_message_enabled'] ) : 0 );
        $settings['lead_message_required'] = $is_basic_tier ? 0 : ( isset( $data['lead_message_required'] ) ? intval( $data['lead_message_required'] ) : 0 );
        $settings['lead_consent_required'] = isset( $data['lead_consent_required'] ) ? intval( $data['lead_consent_required'] ) : 1;

        /**
         * ------------------------------------------------------------
         * Provider-specific settings (Option B)
         * ------------------------------------------------------------
         */
        if ( $provider === 'google' ) {

            if ( isset( $data['google_model'] ) ) {
                $settings['google_model'] = sanitize_text_field( $data['google_model'] );
            }

            if ( isset( $data['fallback_google'] ) ) {
                $settings['fallback_google'] = sanitize_textarea_field( $data['fallback_google'] );
            }

            $settings['google_embedding_source'] = in_array(
                $data['google_embedding_source'] ?? 'google',
                [ 'anthropic', 'openai', 'google', 'xai' ],
                true
            ) ? $data['google_embedding_source'] : 'google';

        } elseif ( $provider === 'anthropic' ) {

            if ( isset( $data['anthropic_model'] ) ) {
                $settings['anthropic_model'] = sanitize_text_field( $data['anthropic_model'] );
            }

            if ( isset( $data['fallback_anthropic'] ) ) {
                $settings['fallback_anthropic'] = sanitize_textarea_field( $data['fallback_anthropic'] );
            }

            $settings['anthropic_embedding_source'] = in_array(
                $data['anthropic_embedding_source'] ?? 'anthropic',
                [ 'anthropic', 'openai', 'google', 'xai' ],
                true
            ) ? $data['anthropic_embedding_source'] : 'anthropic';

        } elseif ( $provider === 'xai' ) {

            if ( isset( $data['xai_model'] ) ) {
                $settings['xai_model'] = class_exists( 'SftcyWordaiXaiSettings' )
                    ? SftcyWordaiXaiSettings::normalize_xai_model_slug( $data['xai_model'] )
                    : sanitize_text_field( $data['xai_model'] );
            }

            if ( isset( $data['fallback_xai'] ) ) {
                $settings['fallback_xai'] = sanitize_textarea_field( $data['fallback_xai'] );
            }

            $settings['xai_embedding_source'] = in_array(
                $data['xai_embedding_source'] ?? 'xai',
                [ 'anthropic', 'openai', 'google', 'xai' ],
                true
            ) ? $data['xai_embedding_source'] : 'xai';

        } else { // openai

            if ( isset( $data['model'] ) ) {
                $settings['model'] = sanitize_text_field( $data['model'] );
            }

            if ( isset( $data['fallback_openai'] ) ) {
                $settings['fallback_openai'] = sanitize_textarea_field( $data['fallback_openai'] );
            }

            $settings['openai_embedding_source'] = in_array(
                $data['openai_embedding_source'] ?? 'openai',
                [ 'anthropic', 'openai', 'google', 'xai' ],
                true
            ) ? $data['openai_embedding_source'] : 'openai';
        }

        /**
         * ------------------------------------------------------------
         * Persist
         * ------------------------------------------------------------
         */
        update_option(
            'sc_wordai_agent_chat_settings',
            wp_parse_args( $settings, $defaults )
        );

        wp_send_json( [
            'status'      => 'success',
            'feedbackMsg' => __( 'Chat settings saved successfully.', 'wordai-pro' ),
        ] );
    }


    /**
     * ===========================================================
     * Reset Chat Settings to Defaults (AJAX Handler)
     * -----------------------------------------------------------
     */
    public static function sc_wordai_agent_reset_chat_settings() {
        check_ajax_referer( 'scwordai_wpnonce', 'security' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json( [
                'status'      => 'error',
                'feedbackMsg' => __( 'Permission denied.', 'wordai-pro' ),
            ] );
        }

        $defaults = SftcyWordaiFrontendAgentChat::wordai_chat_default_settings();
        update_option( 'sc_wordai_agent_chat_settings', $defaults );

        wp_send_json( [
            'status'      => 'success',
            'feedbackMsg' => __( 'Chat settings reset to defaults successfully.', 'wordai-pro' ),
        ] );
    }

    public static function sc_wordai_agent_save_anthropic_embedding_settings() {
        check_ajax_referer( 'scwordai_wpnonce', 'security' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json( [
                'status'      => 'error',
                'feedbackMsg' => __( 'Permission denied.', 'wordai-pro' ),
            ] );
        }

        if ( ! class_exists( 'SftcyWordaiAgentAnthropic' ) ) {
            wp_send_json( [
                'status'      => 'error',
                'feedbackMsg' => __( 'Anthropic embedding module is not available.', 'wordai-pro' ),
            ] );
        }

        $current = SftcyWordaiAgentAnthropic::get_settings();
        $api_key = isset( $_POST['voyage_api_key'] ) ? trim( sanitize_text_field( wp_unslash( $_POST['voyage_api_key'] ) ) ) : '';

        if ( $api_key !== '' ) {
            $current['voyage_api_key'] = $api_key;
        }

        $saved = SftcyWordaiAgentAnthropic::save_settings( $current );

        wp_send_json( [
            'status'      => 'success',
            'feedbackMsg' => __( 'Voyage embedding settings saved successfully.', 'wordai-pro' ),
            'masked_key'  => SftcyWordaiAgentAnthropic::get_voyage_api_key_mask(),
            'configured'  => ! empty( $saved['voyage_api_key'] ),
        ] );
    }

    private static function wordai_agent_lead_statuses() {
        return array( 'new', 'contacted', 'qualified', 'won', 'closed' );
    }

    public static function sc_wordai_agent_update_lead_status() {
        check_ajax_referer( 'scwordai_wpnonce', 'security' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json( array(
                'status'      => 'error',
                'feedbackMsg' => __( 'Permission denied.', 'wordai-pro' ),
            ) );
        }

        global $wpdb;

        $lead_id = isset( $_POST['lead_id'] ) ? absint( $_POST['lead_id'] ) : 0;
        $status  = sanitize_key( $_POST['lead_status'] ?? '' );

        if ( $lead_id <= 0 || ! in_array( $status, self::wordai_agent_lead_statuses(), true ) ) {
            wp_send_json( array(
                'status'      => 'error',
                'feedbackMsg' => __( 'Invalid lead update request.', 'wordai-pro' ),
            ) );
        }

        $updated = $wpdb->update(
            $wpdb->prefix . 'wordai_agent_leads',
            array(
                'status'     => $status,
                'updated_at' => current_time( 'mysql' ),
            ),
            array( 'id' => $lead_id ),
            array( '%s', '%s' ),
            array( '%d' )
        );

        wp_send_json( array(
            'status'      => false === $updated ? 'error' : 'success',
            'feedbackMsg' => false === $updated ? __( 'Lead status update failed.', 'wordai-pro' ) : __( 'Lead status updated.', 'wordai-pro' ),
        ) );
    }

    public static function sc_wordai_agent_delete_lead() {
        check_ajax_referer( 'scwordai_wpnonce', 'security' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json( array(
                'status'      => 'error',
                'feedbackMsg' => __( 'Permission denied.', 'wordai-pro' ),
            ) );
        }

        global $wpdb;

        $lead_id = isset( $_POST['lead_id'] ) ? absint( $_POST['lead_id'] ) : 0;
        if ( $lead_id <= 0 ) {
            wp_send_json( array(
                'status'      => 'error',
                'feedbackMsg' => __( 'Invalid lead delete request.', 'wordai-pro' ),
            ) );
        }

        $deleted = $wpdb->delete( $wpdb->prefix . 'wordai_agent_leads', array( 'id' => $lead_id ), array( '%d' ) );

        wp_send_json( array(
            'status'      => false === $deleted ? 'error' : 'success',
            'feedbackMsg' => false === $deleted ? __( 'Lead delete failed.', 'wordai-pro' ) : __( 'Lead deleted.', 'wordai-pro' ),
        ) );
    }

    public static function sc_wordai_agent_export_leads_csv() {
        check_ajax_referer( 'scwordai_wpnonce', 'security' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_die( esc_html__( 'Permission denied.', 'wordai-pro' ) );
        }

        if ( class_exists( 'SftcyWordaiFeatures' ) && SftcyWordaiFeatures::wordai_is_basic_tier() ) {
            wp_die( esc_html__( 'Lead export is available in WordAI Pro.', 'wordai-pro' ) );
        }

        global $wpdb;

        $table = $wpdb->prefix . 'wordai_agent_leads';
        $rows  = $wpdb->get_results(
            "SELECT id, conversation_id, name, email, phone, company, message, consent_given, source_page_url, source_page_title, first_question, last_question, status, created_at, updated_at
             FROM {$table}
             ORDER BY updated_at DESC, id DESC
             LIMIT 5000",
            ARRAY_A
        );

        nocache_headers();
        header( 'Content-Type: text/csv; charset=utf-8' );
        header( 'Content-Disposition: attachment; filename=wordai-chat-leads-' . gmdate( 'Y-m-d-His' ) . '.csv' );

        $out = fopen( 'php://output', 'w' );
        fputcsv( $out, array( 'ID', 'Conversation ID', 'Name', 'Email', 'Phone', 'Company', 'Message', 'Consent', 'Source URL', 'Source Title', 'First Question', 'Last Question', 'Status', 'Created', 'Updated' ) );

        foreach ( (array) $rows as $row ) {
            fputcsv( $out, array(
                $row['id'],
                $row['conversation_id'],
                $row['name'],
                $row['email'],
                $row['phone'],
                $row['company'],
                $row['message'],
                ! empty( $row['consent_given'] ) ? 'yes' : 'no',
                $row['source_page_url'],
                $row['source_page_title'],
                $row['first_question'],
                $row['last_question'],
                $row['status'],
                $row['created_at'],
                $row['updated_at'],
            ) );
        }

        fclose( $out );
        exit;
    }

} // Class
