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

/**
 * Knowledge Base Admin Handler
 * 
 * Handles all Knowledge Base related AJAX requests and logic.
 *
 * @package    Kitsune_AI_Platform
 * @subpackage Kitsune_AI_Platform/admin
 */
class Kitsune_AI_Platform_Admin_Knowledge_Base extends Kitsune_AI_Platform_Admin_Ajax_Base {

    /**
     * Official Chroma Cloud endpoint.
     */
    private const CHROMA_OFFICIAL_ENDPOINT = 'https://api.trychroma.com';

    /**
     * Initialize the callbacks
     */
    public function __construct() {
        // No-op for now
    }

    /**
     * Register AJAX hooks.
     * 
     * @param Kitsune_AI_Platform_Admin $admin_instance Reference to main admin instance if needed.
     */
    public function register_hooks() {
        add_action('wp_ajax_kitsune_ai_platform_kb_get_sources', array($this, 'ajax_kb_get_sources'));
        add_action('wp_ajax_kitsune_ai_platform_kb_get_wordpress_post_types', array($this, 'ajax_kb_get_wordpress_post_types'));
        add_action('wp_ajax_kitsune_ai_platform_kb_get_wordpress_posts', array($this, 'ajax_kb_get_wordpress_posts'));
        add_action('wp_ajax_kitsune_ai_platform_kb_add_source', array($this, 'ajax_kb_add_source'));
        add_action('wp_ajax_kitsune_ai_platform_kb_delete_source', array($this, 'ajax_kb_delete_source'));
        add_action('wp_ajax_kitsune_ai_platform_kb_sync_source', array($this, 'ajax_kb_sync_source'));
        add_action('wp_ajax_kitsune_ai_platform_kb_search', array($this, 'ajax_kb_search'));
        // Internal action hook — callable from automation actions without an HTTP request
        add_action('kitsune_ai_platform_kb_sync_source', array($this, 'trigger_kb_sync'));
        add_action('wp_ajax_kitsune_ai_platform_kb_get_settings', array($this, 'ajax_kb_get_settings'));
        add_action('wp_ajax_kitsune_ai_platform_kb_save_settings', array($this, 'ajax_kb_save_settings'));
        add_action('wp_ajax_kitsune_ai_platform_kb_reset', array($this, 'ajax_kb_reset'));
        add_action('wp_ajax_kitsune_ai_platform_kb_can_change_embedding_model', array($this, 'ajax_kb_can_change_embedding_model'));
        add_action('wp_ajax_kitsune_ai_platform_kb_get_vector_databases', array($this, 'ajax_kb_get_vector_databases'));
        add_action('wp_ajax_kitsune_ai_platform_kb_update_vector_database', array($this, 'ajax_kb_update_vector_database'));
        add_action('wp_ajax_kitsune_ai_platform_kb_get_embedding_jobs', array($this, 'ajax_kb_get_embedding_jobs'));
        add_action('wp_ajax_kitsune_ai_platform_kb_reembed_job', array($this, 'ajax_kb_reembed_job'));
        add_action('wp_ajax_kitsune_ai_platform_kb_stop_embedding_job', array($this, 'ajax_kb_stop_embedding_job'));
    }

    // =========================================================================
    // HELPER METHODS
    // =========================================================================

    /**
     * Default Knowledge Base settings
     */
    private function get_default_kb_settings() {
        return array(
            'autoSync' => true,
            'syncInterval' => 'daily',
            'chunkSize' => 512,
            'chunkOverlap' => 50,
        );
    }

    /**
     * Default vector database configuration
     */
    private function get_default_vector_databases() {
        return array(
            'default' => 'chroma',
            'databases' => array(
                array(
                    'id' => 'chroma',
                    'name' => __('Chroma DB', 'kitsune-ai-platform'),
                    'description' => __('Use your Chroma instance for listing, semantic search, insert, and delete operations.', 'kitsune-ai-platform'),
                    'icon' => 'fas fa-clone',
                    'colorClass' => 'bg-blue-100 text-blue-600',
                    'connected' => false,
                    'config' => array(
                        'endpoint' => self::CHROMA_OFFICIAL_ENDPOINT,
                        'apiKey' => '',
                        'tenantId' => 'default_tenant',
                        'database' => 'default_database',
                        'collection' => 'ai_platform_kb',
                        'collectionId' => '',
                    ),
                ),
            ),
        );
    }

    /**
     * Sanitize Chroma configuration fields.
     *
     * @param array $config Raw config.
     * @return array
     */
    private function sanitize_chroma_config($config) {
        $endpoint = isset($config['endpoint']) ? esc_url_raw(trim($config['endpoint'])) : '';
        if ($endpoint === '') {
            $endpoint = self::CHROMA_OFFICIAL_ENDPOINT;
        }

        $collection = isset($config['collection']) ? sanitize_key($config['collection']) : 'ai_platform_kb';
        if ($collection === '') {
            $collection = 'ai_platform_kb';
        }

        return array(
            'endpoint' => $endpoint,
            'apiKey' => sanitize_text_field($config['apiKey'] ?? ($config['api_key'] ?? '')),
            'tenantId' => sanitize_text_field($config['tenantId'] ?? ($config['tenant_id'] ?? 'default_tenant')),
            'database' => sanitize_text_field($config['database'] ?? 'default_database'),
            'collection' => $collection,
            'collectionId' => sanitize_text_field($config['collectionId'] ?? ($config['collection_id'] ?? '')),
        );
    }

    /**
     * Normalize vector DB state so only Chroma remains.
     *
     * @param mixed $state Stored state.
     * @return array
     */
    private function normalize_vector_database_state($state) {
        $defaults = $this->get_default_vector_databases();
        $normalized = $defaults;
        $stored = is_array($state) ? $state : array();

        $stored_chroma = null;
        if (!empty($stored['databases']) && is_array($stored['databases'])) {
            foreach ($stored['databases'] as $database) {
                if (!is_array($database)) {
                    continue;
                }
                if (($database['id'] ?? '') === 'chroma') {
                    $stored_chroma = $database;
                    break;
                }
            }
        }

        if (is_array($stored_chroma)) {
            $normalized['databases'][0]['connected'] = !empty($stored_chroma['connected']);
            if (isset($stored_chroma['config']) && is_array($stored_chroma['config'])) {
                $normalized['databases'][0]['config'] = array_merge(
                    $normalized['databases'][0]['config'],
                    $this->sanitize_chroma_config($stored_chroma['config'])
                );
            }
        }

        $normalized['default'] = 'chroma';
        return $normalized;
    }

    /**
     * Get normalized vector DB state from options.
     *
     * @return array
     */
    private function get_vector_database_state() {
        $stored = get_option('kitsune_ai_platform_kb_vector_dbs', $this->get_default_vector_databases());
        $normalized = $this->normalize_vector_database_state($stored);

        if ($normalized !== $stored) {
            update_option('kitsune_ai_platform_kb_vector_dbs', $normalized);
        }

        return $normalized;
    }

    /**
     * Create vector DB client for default database if connected.
     *
     * @param array|null $state Optional normalized state.
     * @return Kitsune_AI_Platform_Vector_DB_Interface|WP_Error
     */
    private function get_vector_db_client($state = null) {
        $state = is_array($state) ? $state : $this->get_vector_database_state();
        $database = isset($state['databases'][0]) && is_array($state['databases'][0]) ? $state['databases'][0] : null;

        if (!$database || ($database['id'] ?? '') !== 'chroma') {
            return new WP_Error('vector_db_missing', __('No supported vector database configured.', 'kitsune-ai-platform'));
        }

        if (empty($database['connected'])) {
            return new WP_Error('vector_db_disconnected', __('Default vector database is not connected.', 'kitsune-ai-platform'));
        }

        $config = isset($database['config']) && is_array($database['config']) ? $database['config'] : array();
        return Kitsune_AI_Platform_Vector_DB_Factory::create('chroma', $config);
    }

    /**
     * Build source text payload for vector DB inserts.
     *
     * @param int $source_id Source post ID.
     * @return string
     */
    private function build_source_document($source_id) {
        $post = get_post($source_id);
        if (!$post) {
            return '';
        }

        $title = $post->post_title;
        $content = $post->post_content;
        $decoded = json_decode($content, true);

        if (is_array($decoded)) {
            if (!empty($decoded['paste']['content'])) {
                $content = $decoded['paste']['content'];
            } elseif (!empty($decoded['crawl']['url'])) {
                /* translators: %s: Crawled source URL. */
                $content = sprintf(__('Crawled URL: %s', 'kitsune-ai-platform'), $decoded['crawl']['url']);
            } else {
                $wordpress_selection = $this->normalize_wordpress_source_selection($decoded);
                if (!empty($wordpress_selection)) {
                    $content = sprintf(
                        /* translators: %s: Comma-separated selected WordPress source types. */
                        __('WordPress data source: %s', 'kitsune-ai-platform'),
                        implode(', ', array_map('sanitize_text_field', array_keys($wordpress_selection)))
                    );
                }
            }
        }

        $plain_text = wp_strip_all_tags($content);
        $plain_text = trim($plain_text);
        if ($plain_text === '') {
            return trim($title);
        }

        return trim($title . "\n\n" . $plain_text);
    }

    /**
     * Normalize WordPress source selection payload.
     *
     * @param array $payload Source payload.
     * @return array
     */
    private function normalize_wordpress_source_selection($payload) {
        $selection = array();
        if (!is_array($payload)) {
            return $selection;
        }

        if (!empty($payload['postTypeSelections']) && is_array($payload['postTypeSelections'])) {
            foreach ($payload['postTypeSelections'] as $post_type => $config) {
                $post_type = sanitize_key($post_type);
                if ($post_type === '' || !post_type_exists($post_type)) {
                    continue;
                }

                $mode = isset($config['mode']) ? sanitize_key($config['mode']) : 'all';
                if (!in_array($mode, array('all', 'include', 'exclude'), true)) {
                    $mode = 'all';
                }

                $post_ids = array();
                if (!empty($config['postIds']) && is_array($config['postIds'])) {
                    $post_ids = array_values(array_unique(array_filter(array_map('intval', $config['postIds']), static function ($id) {
                        return $id > 0;
                    })));
                }

                $selection[$post_type] = array(
                    'mode' => $mode,
                    'post_ids' => $post_ids,
                );
            }
        }

        if (!empty($selection)) {
            return $selection;
        }

        if (!empty($payload['postTypes']) && is_array($payload['postTypes'])) {
            foreach ($payload['postTypes'] as $post_type) {
                $post_type = sanitize_key($post_type);
                if ($post_type === '' || !post_type_exists($post_type)) {
                    continue;
                }

                $selection[$post_type] = array(
                    'mode' => 'all',
                    'post_ids' => array(),
                );
            }
        }

        return $selection;
    }

    /**
     * Build source documents for embedding sync.
     *
     * @param int $source_id Source post ID.
     * @return array
     */
    private function build_source_documents($source_id) {
        $source = get_post($source_id);
        if (!$source) {
            return array();
        }

        $documents = array();
        $source_type = get_post_meta($source_id, '_ai_platform_kb_type', true);
        $payload = json_decode($source->post_content, true);

        if ($source_type === 'wordpress' && is_array($payload)) {
            $wordpress_selection = $this->normalize_wordpress_source_selection($payload);

            foreach ($wordpress_selection as $post_type => $config) {
                $mode = $config['mode'] ?? 'all';
                $post_ids = is_array($config['post_ids'] ?? null) ? $config['post_ids'] : array();

                $query = array(
                    'post_type' => $post_type,
                    'post_status' => array('publish', 'private'),
                    'posts_per_page' => -1,
                    'orderby' => 'modified',
                    'order' => 'DESC',
                    'ignore_sticky_posts' => true,
                    'no_found_rows' => true,
                    'update_post_meta_cache' => false,
                    'update_post_term_cache' => false,
                );

                if ($mode === 'include') {
                    if (empty($post_ids)) {
                        continue;
                    }
                    $query['post__in'] = $post_ids;
                    $query['orderby'] = 'post__in';
                } elseif ($mode === 'exclude' && !empty($post_ids)) {
                    $query['post__not_in'] = $post_ids;
                }

                $posts = get_posts($query);
                foreach ($posts as $post) {
                    $content = trim($post->post_title . "\n\n" . wp_strip_all_tags($post->post_content));
                    if ($content === '') {
                        continue;
                    }

                    $documents[] = array(
                        'content' => $content,
                        'post_id' => intval($post->ID),
                    );
                }
            }
        }

        if (!empty($documents)) {
            return $documents;
        }

        $single_document = $this->build_source_document($source_id);
        if ($single_document === '') {
            return array();
        }

        return array(
            array(
                'content' => $single_document,
                'post_id' => null,
            ),
        );
    }

    /**
     * Get chunk settings for embedding ingestion.
     *
     * @return array
     */
    private function get_kb_chunking_rules() {
        $settings = get_option('kitsune_ai_platform_kb_settings', $this->get_default_kb_settings());
        $chunk_size = max(128, min(2048, intval($settings['chunkSize'] ?? 512)));
        $chunk_overlap = max(0, min(200, intval($settings['chunkOverlap'] ?? 50)));

        if ($chunk_overlap >= $chunk_size) {
            $chunk_overlap = max(0, $chunk_size - 1);
        }

        return array(
            'size' => $chunk_size,
            'overlap' => $chunk_overlap,
        );
    }

    /**
     * Split text into overlapping chunks.
     *
     * @param string $text Chunk source text.
     * @param int    $chunk_size Chunk size in words.
     * @param int    $chunk_overlap Chunk overlap in words.
     * @return array
     */
    private function chunk_text_for_embeddings($text, $chunk_size, $chunk_overlap) {
        $normalized = trim(preg_replace('/\s+/', ' ', wp_strip_all_tags((string) $text)));
        if ($normalized === '') {
            return array();
        }

        $words = preg_split('/\s+/', $normalized);
        if (!is_array($words) || empty($words)) {
            return array();
        }

        $chunk_size = max(1, intval($chunk_size));
        $chunk_overlap = max(0, min($chunk_size - 1, intval($chunk_overlap)));
        $step = max(1, $chunk_size - $chunk_overlap);

        $chunks = array();
        $total = count($words);
        for ($start = 0; $start < $total; $start += $step) {
            $slice = array_slice($words, $start, $chunk_size);
            if (empty($slice)) {
                break;
            }

            $chunk = trim(implode(' ', $slice));
            if ($chunk !== '') {
                $chunks[] = $chunk;
            }

            if ($start + $chunk_size >= $total) {
                break;
            }
        }

        return $chunks;
    }

    /**
     * Generate an embedding using the configured provider.
     *
     * @param string $text Input text.
     * @param string $model Embedding model ID.
     * @return array|WP_Error
     */
    private function generate_embedding_for_text($text, $model) {
        if (!is_string($text) || trim($text) === '') {
            return new WP_Error('kb_empty_embedding_input', __('Embedding input is required.', 'kitsune-ai-platform'));
        }

        if (!is_string($model) || $model === '') {
            return new WP_Error('kb_embedding_model_missing', __('Embedding model is required.', 'kitsune-ai-platform'));
        }

        $api = new Kitsune_AI_Platform_API();
        $response = $api->embed(array(
            'model' => $model,
            'input' => $text,
        ));

        if (empty($response['success'])) {
            return new WP_Error(
                'kb_embedding_failed',
                $response['message'] ?? __('Failed to generate embeddings.', 'kitsune-ai-platform')
            );
        }

        $embedding = $response['data']['embedding'] ?? null;
        if (!is_array($embedding) || empty($embedding)) {
            return new WP_Error('kb_embedding_missing', __('Embedding vector is missing.', 'kitsune-ai-platform'));
        }

        return $embedding;
    }

    /**
     * Resolve default embedding model ID.
     *
     * @return string
     */
    private function get_default_embedding_model() {
        $defaults = get_option('kitsune_ai_platform_default_models', array());
        $model = isset($defaults['embeddings']) ? $defaults['embeddings'] : '';
        return $this->extract_model_id($model);
    }

    /**
     * Extract model ID from provider/model payloads.
     *
     * @param mixed $value
     * @return string
     */
    private function extract_model_id($value) {
        if (is_array($value)) {
            return sanitize_text_field($value['model'] ?? '');
        }

        $value = is_string($value) ? trim($value) : '';
        if ($value === '') {
            return '';
        }

        $parts = explode(':', $value, 2);
        return sanitize_text_field(count($parts) === 2 ? $parts[1] : $value);
    }

    /**
     * Sync a source into default vector DB.
     *
     * @param int                    $source_id Source ID.
     * @param Kitsune_AI_Platform_KB_Source_Model $model Source model.
     * @return array|WP_Error
     */
    private function sync_source_to_default_vector_db($source_id, $model) {
        $embedding_model = $this->get_default_embedding_model();
        if ($embedding_model === '') {
            return new WP_Error(
                'kb_embedding_model_missing',
                __('Select a default embedding model before syncing sources.', 'kitsune-ai-platform')
            );
        }

        $state = $this->get_vector_database_state();
        $client = $this->get_vector_db_client($state);
        if (is_wp_error($client)) {
            return $client;
        }

        $source = get_post($source_id);
        if (!$source) {
            return new WP_Error('kb_source_not_found', __('Source not found.', 'kitsune-ai-platform'));
        }

        $documents = $this->build_source_documents($source_id);
        if (empty($documents)) {
            return new WP_Error('kb_empty_source', __('Unable to extract source content for syncing.', 'kitsune-ai-platform'));
        }

        $chunking_rules = $this->get_kb_chunking_rules();
        $chunks = array();

        foreach ($documents as $document) {
            $document_chunks = $this->chunk_text_for_embeddings(
                $document['content'] ?? '',
                $chunking_rules['size'],
                $chunking_rules['overlap']
            );

            foreach ($document_chunks as $chunk_text) {
                $chunks[] = array(
                    'content' => $chunk_text,
                    'post_id' => array_key_exists('post_id', $document) ? $document['post_id'] : null,
                );
            }
        }

        if (empty($chunks)) {
            return new WP_Error('kb_empty_chunks', __('Unable to generate chunks for syncing.', 'kitsune-ai-platform'));
        }

        $cleanup_result = $this->delete_source_from_vector_db($source_id);
        if (empty($cleanup_result['success'])) {
            return new WP_Error(
                'kb_sync_cleanup_failed',
                $cleanup_result['message'] ?? __('Failed to clear old source embeddings before syncing.', 'kitsune-ai-platform')
            );
        }

        $total_chunks = count($chunks);
        $record_ids = array();

        foreach ($chunks as $index => $chunk) {
            $record_id = 'source-' . intval($source_id) . '-' . ($index + 1);
            $chunk_post_id = $chunk['post_id'];
            if ($chunk_post_id === null || $chunk_post_id === '') {
                $chunk_post_id = null;
            } else {
                $chunk_post_id = intval($chunk_post_id);
            }

            $metadata = array(
                'source_id' => intval($source_id),
                'source_name' => $source->post_title,
                'model' => $embedding_model,
                'chunks' => $total_chunks,
                'chunk_index' => $index + 1,
                'updated_at' => current_time('mysql'),
            );
            if ($chunk_post_id !== null) {
                $metadata['post_id'] = $chunk_post_id;
            }

            $embedding = $this->generate_embedding_for_text($chunk['content'], $embedding_model);
            if (is_wp_error($embedding)) {
                foreach ($record_ids as $created_record_id) {
                    $client->delete_record($created_record_id);
                }
                return $embedding;
            }

            $insert_payload = array(
                'id' => $record_id,
                'source_id' => intval($source_id),
                'content' => $chunk['content'],
                'model' => $embedding_model,
                'embedding' => $embedding,
                'metadata' => $metadata,
            );
            if ($chunk_post_id !== null) {
                $insert_payload['post_id'] = $chunk_post_id;
            }

            $insert_result = $client->insert_record($insert_payload);

            if (empty($insert_result['success'])) {
                foreach ($record_ids as $created_record_id) {
                    $client->delete_record($created_record_id);
                }
                $message = isset($insert_result['message']) ? $insert_result['message'] : __('Failed to sync source to vector database.', 'kitsune-ai-platform');
                return new WP_Error('kb_sync_failed', $message);
            }

            $record_ids[] = $record_id;
        }

        update_post_meta($source_id, '_ai_platform_kb_vector_ids', $record_ids);
        return $model->update_status($source_id, 'synced', $total_chunks);
    }

    /**
     * Remove source records from default vector DB.
     *
     * @param int $source_id Source ID.
     * @return array
     */
    private function delete_source_from_vector_db($source_id) {
        $vector_ids = get_post_meta($source_id, '_ai_platform_kb_vector_ids', true);
        if (!is_array($vector_ids) || empty($vector_ids)) {
            return array('success' => true, 'deleted' => 0);
        }

        $client = $this->get_vector_db_client();
        if (is_wp_error($client)) {
            return array('success' => false, 'message' => $client->get_error_message(), 'deleted' => 0);
        }

        $deleted = 0;
        foreach ($vector_ids as $record_id) {
            $result = $client->delete_record($record_id);
            if (!empty($result['success'])) {
                $deleted++;
            }
        }

        delete_post_meta($source_id, '_ai_platform_kb_vector_ids');

        return array(
            'success' => true,
            'deleted' => $deleted,
        );
    }

    // =========================================================================
    // AJAX HANDLERS
    // =========================================================================

    /**
     * AJAX: Get Knowledge Base Sources
     */
    public function ajax_kb_get_sources() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        // Model loaded via Autoloader
        $model = new Kitsune_AI_Platform_KB_Source_Model();

        $page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
        $per_page = isset($_GET['per_page']) ? min(100, max(1, intval($_GET['per_page']))) : 50;
        $search = isset($_GET['search']) ? sanitize_text_field(wp_unslash($_GET['search'])) : '';
        $offset = ($page - 1) * $per_page;

        $sources = $model->get_all(array(
            'limit' => $per_page,
            'offset' => $offset,
            'search' => $search,
        ));

        wp_send_json_success(array(
            'data' => array(
                'sources' => $sources,
                'page' => $page,
                'per_page' => $per_page
            )
        ));
    }

    /**
     * AJAX: Get available WordPress post types for KB ingestion
     */
    public function ajax_kb_get_wordpress_post_types() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        $excluded_post_types = array(
            'attachment',
            'revision',
            'nav_menu_item',
            'custom_css',
            'customize_changeset',
            'oembed_cache',
            'wp_block',
            'wp_template',
            'wp_template_part',
            'wp_navigation',
            'wp_global_styles',
            'wp_font_family',
            'wp_font_face',
            'kitsune_ai_kb_source',
        );

        $post_type_objects = get_post_types(array('show_ui' => true), 'objects');
        $post_types = array();

        foreach ($post_type_objects as $post_type => $post_type_object) {
            if (in_array($post_type, $excluded_post_types, true)) {
                continue;
            }

            $label = isset($post_type_object->labels->name) && $post_type_object->labels->name !== ''
                ? $post_type_object->labels->name
                : ucfirst($post_type);

            $description = trim((string) ($post_type_object->description ?? ''));
            if ($description === '') {
                /* translators: %s: Post type label */
                $description = sprintf(__('All %s content', 'kitsune-ai-platform'), strtolower($label));
            }

            $post_types[] = array(
                'id' => $post_type,
                'name' => $label,
                'description' => sanitize_text_field($description),
            );
        }

        usort($post_types, static function ($left, $right) {
            return strcasecmp($left['name'], $right['name']);
        });

        wp_send_json_success(array(
            'post_types' => $post_types,
        ));
    }

    /**
     * AJAX: Get posts for a selected WordPress post type
     */
    public function ajax_kb_get_wordpress_posts() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        $post_type = isset($_GET['post_type']) ? sanitize_key(wp_unslash($_GET['post_type'])) : '';
        if ($post_type === '' || !post_type_exists($post_type)) {
            wp_send_json_error(array('message' => __('Invalid post type', 'kitsune-ai-platform')));
            return;
        }

        $excluded_post_types = array('attachment', 'revision', 'kitsune_ai_kb_source');
        if (in_array($post_type, $excluded_post_types, true)) {
            wp_send_json_error(array('message' => __('This post type is not available for ingestion', 'kitsune-ai-platform')));
            return;
        }

        $post_type_object = get_post_type_object($post_type);
        if (!$post_type_object || empty($post_type_object->show_ui)) {
            wp_send_json_error(array('message' => __('This post type is not available for ingestion', 'kitsune-ai-platform')));
            return;
        }

        $query = array(
            'post_type'              => $post_type,
            'post_status'            => array('publish', 'private'),
            'posts_per_page'         => isset($_GET['per_page']) ? min(100, max(1, intval($_GET['per_page']))) : 50,
            'orderby'                => 'modified',
            'order'                  => 'DESC',
            'ignore_sticky_posts'    => true,
            'suppress_filters'       => false,
            'no_found_rows'          => true,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
        );

        $search = isset($_GET['search']) ? sanitize_text_field(wp_unslash($_GET['search'])) : '';
        if ($search !== '') {
            $query['s'] = $search;
        }

        $posts = get_posts($query);
        $items = array();

        foreach ($posts as $post) {
            $title = get_the_title($post->ID);
            if ($search !== '' && stripos($title, $search) === false) {
                continue;
            }
            if ($title === '') {
                /* translators: %d: Post ID */
                $title = sprintf(__('Untitled (%d)', 'kitsune-ai-platform'), intval($post->ID));
            }

            $items[] = array(
                'id' => intval($post->ID),
                'title' => $title,
            );
        }

        wp_send_json_success(array(
            'posts' => $items,
        ));
    }

    /**
     * AJAX: Add Knowledge Base Source
     */
    public function ajax_kb_add_source() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        $data = $this->get_post_payload('data', array());
        if (!$data) $data = map_deep(wp_unslash($_POST), 'sanitize_text_field');

        if ($this->get_default_embedding_model() === '') {
            wp_send_json_error(array(
                'message' => __('Select a default embedding model before adding knowledge sources.', 'kitsune-ai-platform')
            ));
            return;
        }

        if (is_wp_error($this->get_vector_db_client())) {
            wp_send_json_error(array(
                'message' => __('Connect the default vector database before adding knowledge sources.', 'kitsune-ai-platform')
            ));
            return;
        }

        // Model loaded via Autoloader
        $model = new Kitsune_AI_Platform_KB_Source_Model();

        $result = $model->create($data);

        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
        } else {
            $sync_result = $this->sync_source_to_default_vector_db($result, $model);
            if (is_wp_error($sync_result)) {
                $model->update_status($result, 'pending', 0);
            }

            $sources = $model->get_all(array('limit' => 50, 'offset' => 0));
            $created = null;
            foreach ($sources as $source) {
                if (intval($source['id']) === intval($result)) {
                    $created = $source;
                    break;
                }
            }
            if (!$created) {
                $created = array('id' => $result);
            }

            $message = __('Source added successfully', 'kitsune-ai-platform');
            if (is_wp_error($sync_result)) {
                $message .= ' ' . sprintf(__('Sync skipped: %s', 'kitsune-ai-platform'), $sync_result->get_error_message());
            }

            wp_send_json_success(array(
                'message' => $message,
                'source' => $created
            ));
        }
    }

    /**
     * AJAX: Delete Knowledge Base Source
     */
    public function ajax_kb_delete_source() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        $id = isset($_POST['id']) ? intval($_POST['id']) : 0;
        if (!$id) {
            wp_send_json_error(array('message' => __('ID required', 'kitsune-ai-platform')));
            return;
        }

        // Model loaded via Autoloader
        $model = new Kitsune_AI_Platform_KB_Source_Model();

        $vector_delete = $this->delete_source_from_vector_db($id);

        if ($model->delete($id)) {
            $message = __('Source deleted', 'kitsune-ai-platform');
            if (!$vector_delete['success']) {
                $message .= ' ' . sprintf(
                    __('Vector cleanup warning: %s', 'kitsune-ai-platform'),
                    $vector_delete['message'] ?? __('Unknown error', 'kitsune-ai-platform')
                );
            }
            wp_send_json_success(array('message' => $message));
        } else {
            wp_send_json_error(array('message' => __('Failed to delete source', 'kitsune-ai-platform')));
        }
    }

    /**
     * Internal action hook handler: sync a KB source triggered by automation.
     * Fires on 'ai_platform_kb_sync_source' action with the source post ID.
     *
     * @param int $source_id
     */
    public function trigger_kb_sync($source_id) {
        $source_id = intval($source_id);
        if (!$source_id) return;
        if ($this->get_default_embedding_model() === '') return; // No model configured — skip silently
        $model = new Kitsune_AI_Platform_KB_Source_Model();
        $this->sync_source_to_default_vector_db($source_id, $model);
    }

    /**
     * AJAX: Sync Knowledge Base Source
     */
    public function ajax_kb_sync_source() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        $id = isset($_POST['id']) ? intval($_POST['id']) : 0;
        if (!$id) {
            wp_send_json_error(array('message' => __('ID required', 'kitsune-ai-platform')));
            return;
        }

        if ($this->get_default_embedding_model() === '') {
            wp_send_json_error(array(
                'message' => __('Select a default embedding model before syncing sources.', 'kitsune-ai-platform')
            ));
            return;
        }

        // Model loaded via Autoloader
        $model = new Kitsune_AI_Platform_KB_Source_Model();

        $updated = $this->sync_source_to_default_vector_db($id, $model);

        if (is_wp_error($updated)) {
            wp_send_json_error(array('message' => $updated->get_error_message()));
        } else {
            wp_send_json_success(array(
                'message' => __('Source synced', 'kitsune-ai-platform'),
                'source' => $updated
            ));
        }
    }

    /**
     * AJAX: Semantic Search
     */
    public function ajax_kb_search() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        $query = isset($_POST['query']) ? sanitize_text_field(wp_unslash($_POST['query'])) : '';
        if ($query === '') {
            wp_send_json_error(array('message' => __('Query is required', 'kitsune-ai-platform')));
            return;
        }

        if ($this->get_default_embedding_model() === '') {
            wp_send_json_error(array(
                'message' => __('Select a default embedding model before using semantic search.', 'kitsune-ai-platform')
            ));
            return;
        }

        $start = microtime(true);
        $state = $this->get_vector_database_state();
        $client = $this->get_vector_db_client($state);

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

        $embedding = $this->generate_embedding_for_text($query, $this->get_default_embedding_model());
        if (is_wp_error($embedding)) {
            wp_send_json_error(array(
                'message' => $embedding->get_error_message(),
            ));
            return;
        }

        $search_result = $client->vector_search($query, array(
            'limit' => 8,
            'query_embedding' => $embedding,
        ));
        if (empty($search_result['success'])) {
            wp_send_json_error(array(
                'message' => $search_result['message'] ?? __('Semantic search failed.', 'kitsune-ai-platform')
            ));
            return;
        }

        $items = array();
        foreach ($search_result['data'] as $match) {
            $metadata = isset($match['metadata']) && is_array($match['metadata']) ? $match['metadata'] : array();
            $items[] = array(
                'source' => $metadata['source_name'] ?? __('Chroma Record', 'kitsune-ai-platform'),
                'content' => $match['content'] ?? '',
                'score' => $match['score'] ?? 0,
            );
        }

        $latency = (int) round((microtime(true) - $start) * 1000);

        wp_send_json_success(array(
            'latency' => $latency,
            'vectorCount' => count($items),
            'items' => $items,
        ));
    }

    /**
     * AJAX: Get Knowledge Base Settings
     */
    public function ajax_kb_get_settings() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        $settings = get_option('kitsune_ai_platform_kb_settings', $this->get_default_kb_settings());
        $settings['lastReset'] = get_option('kitsune_ai_platform_kb_last_reset', '');
        wp_send_json_success($settings);
    }

    /**
     * AJAX: Save Knowledge Base Settings
     */
    public function ajax_kb_save_settings() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        $data = $this->get_post_payload('data', array());
        if (!$data) {
            $data = map_deep(wp_unslash($_POST), 'sanitize_text_field');
        }
        $current = get_option('kitsune_ai_platform_kb_settings', $this->get_default_kb_settings());
        $chunk_size = max(128, min(2048, intval($data['chunkSize'] ?? ($current['chunkSize'] ?? 512))));
        $chunk_overlap = max(0, min(200, intval($data['chunkOverlap'] ?? ($current['chunkOverlap'] ?? 50))));

        if ($chunk_overlap >= $chunk_size) {
            $chunk_overlap = max(0, $chunk_size - 1);
        }

        $settings = array(
            'autoSync' => array_key_exists('autoSync', $data) ? !empty($data['autoSync']) : !empty($current['autoSync']),
            'syncInterval' => sanitize_text_field($data['syncInterval'] ?? ($current['syncInterval'] ?? 'daily')),
            'chunkSize' => $chunk_size,
            'chunkOverlap' => $chunk_overlap,
        );

        update_option('kitsune_ai_platform_kb_settings', $settings);
        $settings['lastReset'] = get_option('kitsune_ai_platform_kb_last_reset', '');
        wp_send_json_success(array('message' => __('Settings saved', 'kitsune-ai-platform'), 'settings' => $settings));
    }

    /**
     * AJAX: Reset Knowledge Base.
     *
     * For Chroma DB: creates a fresh collection (old data is left intact in Chroma
     * so no dimension-conflict issues arise) and updates the stored config to point
     * to the new collection. Only local source posts and embedding rows are deleted.
     */
    public function ajax_kb_reset() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        global $wpdb;

        $source_ids = get_posts(array(
            'post_type'      => Kitsune_AI_Platform_KB_Source_Model::POST_TYPE,
            'post_status'    => array('private', 'publish', 'draft'),
            'posts_per_page' => -1,
            'fields'         => 'ids',
            'no_found_rows'  => true,
        ));

        // Create a fresh collection in the vector DB so dimension constraints from
        // the previous embedding model do not carry over. Update the stored config
        // so all subsequent operations target the new collection.
        $client = $this->get_vector_db_client();
        if (!is_wp_error($client)) {
            $result = $client->reset_collection();
            if (!empty($result['success']) && !empty($result['collection'])) {
                $state = $this->get_vector_database_state();
                foreach ($state['databases'] as &$db) {
                    if (($db['id'] ?? '') === 'chroma') {
                        $db['config']['collection']   = $result['collection'];
                        $db['config']['collectionId'] = $result['collectionId'] ?? '';
                        break;
                    }
                }
                unset($db);
                update_option('kitsune_ai_platform_kb_vector_dbs', $state);
            }
        }

        // Wipe local embedding rows.
        $wpdb->query( $wpdb->prepare( "DELETE FROM %i", $wpdb->prefix . 'kitsune_ai_platform_embeddings' ) );

        // Delete all local KB source posts (force = true skips trash).
        foreach ($source_ids as $source_id) {
            wp_delete_post($source_id, true);
        }

        $reset_time = current_time('mysql');
        update_option('kitsune_ai_platform_kb_last_reset', $reset_time);

        wp_send_json_success(array(
            'message'    => __('Knowledge base reset successfully.', 'kitsune-ai-platform'),
            'reset_time' => $reset_time,
        ));
    }

    /**
     * AJAX: Check whether the default embedding model may be changed.
     *
     * Returns can_change=false when at least one KB source was synced
     * after the last reset (or ever, if no reset has been recorded).
     */
    public function ajax_kb_can_change_embedding_model() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        $last_reset = get_option('kitsune_ai_platform_kb_last_reset', '');

        $meta_query = array(
            array(
                'key'   => '_ai_platform_kb_status',
                'value' => 'synced',
            ),
        );

        if ($last_reset !== '') {
            $meta_query[] = array(
                'key'     => '_ai_platform_kb_last_updated',
                'value'   => $last_reset,
                'compare' => '>=',
                'type'    => 'DATETIME',
            );
        }

        $synced = get_posts(array(
            'post_type'      => Kitsune_AI_Platform_KB_Source_Model::POST_TYPE,
            'post_status'    => array('private', 'publish', 'draft'),
            'posts_per_page' => 1,
            'fields'         => 'ids',
            'no_found_rows'  => false,
            'meta_query'     => $meta_query,
        ));

        $can_change = empty($synced);

        wp_send_json_success(array(
            'can_change' => $can_change,
            'message'    => $can_change
                ? ''
                : __('Cannot change the embedding model while knowledge base sources are synced. Reset the knowledge base first.', 'kitsune-ai-platform'),
        ));
    }

    /**
     * AJAX: Get Vector Databases
     */
    public function ajax_kb_get_vector_databases() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        $data = $this->get_vector_database_state();
        wp_send_json_success($data);
    }

    /**
     * AJAX: Update Vector Database
     */
    public function ajax_kb_update_vector_database() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        $id = isset($_POST['id']) ? sanitize_key(wp_unslash($_POST['id'])) : '';
        $action = isset($_POST['update_action']) ? sanitize_text_field(wp_unslash($_POST['update_action'])) : '';
        $config = $this->get_post_payload('config', array());
        if (!$config && !empty($_POST['config'])) {
            $config = is_array($_POST['config']) ? map_deep(wp_unslash($_POST['config']), 'sanitize_text_field') : array();
        }
        if (!is_array($config)) {
            $config = array();
        }

        if ($id === '' || $action === '') {
            wp_send_json_error(array('message' => __('ID and action are required', 'kitsune-ai-platform')));
            return;
        }

        if ($id !== 'chroma') {
            wp_send_json_error(array('message' => __('Only Chroma DB is supported.', 'kitsune-ai-platform')));
            return;
        }

        $data = $this->get_vector_database_state();

        if ($action === 'set_default') {
            $data['default'] = 'chroma';
        }

        if ($action === 'connect') {
            $target_index = -1;
            foreach ($data['databases'] as $index => $db) {
                if (($db['id'] ?? '') !== $id) {
                    continue;
                }

                $target_index = $index;
                $data['databases'][$index]['config'] = $this->sanitize_chroma_config($db['config'] ?? array());
                break;
            }

            if ($target_index < 0) {
                wp_send_json_error(array('message' => __('Database configuration not found.', 'kitsune-ai-platform')));
                return;
            }

            $target_db = $data['databases'][$target_index];
            $client = Kitsune_AI_Platform_Vector_DB_Factory::create('chroma', $target_db['config']);
            if (is_wp_error($client)) {
                wp_send_json_error(array('message' => $client->get_error_message()));
                return;
            }

            if (method_exists($client, 'probe_connection')) {
                $probe = $client->probe_connection();
            } else {
                $probe = $client->list_records(array('limit' => 1, 'offset' => 0));
            }
            if (empty($probe['success'])) {
                $message = $probe['message'] ?? __('Unable to reach Chroma DB.', 'kitsune-ai-platform');
                wp_send_json_error(array(
                    'message' => sprintf(__('Connection failed: %s', 'kitsune-ai-platform'), $message),
                ));
                return;
            }

            $data['databases'][$target_index]['connected'] = true;
        } elseif ($action === 'disconnect') {
            foreach ($data['databases'] as &$db) {
                if ($db['id'] === $id) {
                    $db['connected'] = false;
                }
            }
            unset($db);
        }

        if ($action === 'configure') {
            $clean_config = $this->sanitize_chroma_config($config);
            foreach ($data['databases'] as &$db) {
                if ($db['id'] === $id) {
                    $db['config'] = array_merge(
                        isset($db['config']) && is_array($db['config']) ? $db['config'] : array(),
                        $clean_config
                    );
                }
            }
            unset($db);
        }

        update_option('kitsune_ai_platform_kb_vector_dbs', $data);
        $message = __('Vector database settings updated.', 'kitsune-ai-platform');
        if ($action === 'connect') {
            $message = __('Connected to Chroma DB successfully.', 'kitsune-ai-platform');
        } elseif ($action === 'disconnect') {
            $message = __('Disconnected from Chroma DB.', 'kitsune-ai-platform');
        } elseif ($action === 'configure') {
            $message = __('Chroma DB configuration saved.', 'kitsune-ai-platform');
        }

        wp_send_json_success(array_merge($data, array('message' => $message)));
    }

    /**
     * AJAX: Get Embedding Jobs
     */
    public function ajax_kb_get_embedding_jobs() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        $state = $this->get_vector_database_state();
        $client = $this->get_vector_db_client($state);
        if (is_wp_error($client)) {
            wp_send_json_success(array());
            return;
        }

        $records = $client->list_records(array('limit' => 100, 'offset' => 0));
        if (empty($records['success'])) {
            wp_send_json_error(array(
                'message' => $records['message'] ?? __('Failed to fetch embeddings from vector database.', 'kitsune-ai-platform')
            ));
            return;
        }

        $jobs = array();
        foreach ($records['data'] as $record) {
            $metadata = isset($record['metadata']) && is_array($record['metadata']) ? $record['metadata'] : array();
            $document = isset($record['document']) ? (string) $record['document'] : '';
            $chunks = isset($metadata['chunks']) ? intval($metadata['chunks']) : max(1, (int) ceil(str_word_count($document) / 120));

            $jobs[] = array(
                'id' => $record['id'],
                'source' => $metadata['source_name'] ?? __('Chroma Record', 'kitsune-ai-platform'),
                'chunks' => $chunks,
                'db' => __('Chroma DB', 'kitsune-ai-platform'),
                'status' => __('Completed', 'kitsune-ai-platform'),
                'progress' => 100,
                'preview' => wp_trim_words($document, 20, '...'),
            );
        }

        wp_send_json_success($jobs);
    }

    /**
     * AJAX: Re-embed a Job
     */
    public function ajax_kb_reembed_job() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        $id = isset($_POST['id']) ? sanitize_text_field(wp_unslash($_POST['id'])) : '';
        if ($id === '') {
            wp_send_json_error(array('message' => __('ID required', 'kitsune-ai-platform')));
            return;
        }

        wp_send_json_success(array(
            'message' => __('Re-embedding is managed by the connected vector database.', 'kitsune-ai-platform'),
            'jobs' => array()
        ));
    }

    /**
     * AJAX: Stop an Embedding Job
     */
    public function ajax_kb_stop_embedding_job() {
        if (!$this->verify_admin_request('knowledge_base')) {
            return;
        }

        $id = isset($_POST['id']) ? sanitize_text_field(wp_unslash($_POST['id'])) : '';
        if ($id === '') {
            wp_send_json_error(array('message' => __('ID required', 'kitsune-ai-platform')));
            return;
        }

        wp_send_json_success(array(
            'message' => __('Stopping embedding jobs is not available for external vector DB records.', 'kitsune-ai-platform'),
            'jobs' => array()
        ));
    }
}
