<?php
/**
 * Embedding service.
 *
 * Manages embeddings for knowledge chunks. Actual embedding creation
 * is done in the browser using Transformers.js.
 *
 * @package Inqyra
 */

// If this file is called directly, abort.
if (!defined('WPINC')) {
    die;
}

/**
 * Embedder service class.
 */
class Inqyra_Embedder {

    /**
     * The embedding model used by Transformers.js in the browser.
     */
    const MODEL = 'Xenova/paraphrase-multilingual-MiniLM-L12-v2';

    /**
     * Vector dimension size (must match the model output).
     */
    const VECTOR_DIM = 384;

    /**
     * Get the model name.
     *
     * @return string The model name.
     */
    public function get_model() {
        return self::MODEL;
    }

    /**
     * Get the vector dimension.
     *
     * @return int The vector dimension.
     */
    public function get_vector_dim() {
        return self::VECTOR_DIM;
    }

    /**
     * Check if embeddings are available.
     * With browser-based embeddings, this is always true.
     *
     * @return bool Always true for browser-based embeddings.
     */
    public function is_available() {
        return true;
    }

    /**
     * Get the provider name.
     *
     * @return string The provider name.
     */
    public function get_provider() {
        return 'browser';
    }

    /**
     * Get chunks that need embeddings.
     *
     * @param int $limit Maximum number of chunks to return.
     * @param int|null $agent_id Optional agent ID to filter by.
     * @return array Array of chunks needing embeddings.
     */
    public function get_chunks_needing_embeddings($limit = 10, $agent_id = null) {
        global $wpdb;

        $where = "WHERE needs_embedding = 1";
        $params = [];

        if ($agent_id !== null && $agent_id > 0) {
            $where .= " AND agent_id = %d";
            $params[] = $agent_id;
        }

        $params[] = $limit;

        $sql = 'SELECT id, content FROM %i '
                . $where
                . ' ORDER BY created_at ASC
                LIMIT %d';

        // Prepend the table name as the first parameter.
        array_unshift($params, $wpdb->prefix . 'inqyra_knowledge_chunks');

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $sql is built with placeholders and passed to prepare() with $params. Must be real-time.
        return $wpdb->get_results($wpdb->prepare($sql, ...$params), ARRAY_A);
    }

    /**
     * Save an embedding for a chunk.
     *
     * @param int   $chunk_id  The chunk ID.
     * @param array $embedding The embedding vector.
     * @return bool Whether the save was successful.
     */
    public function save_embedding($chunk_id, $embedding) {
        $vector_store = new Inqyra_Vector_Store();
        return $vector_store->store_embedding($chunk_id, $embedding, self::MODEL);
    }

    /**
     * Mark a chunk as failed to embed.
     *
     * @param int $chunk_id The chunk ID.
     * @return bool Whether the update was successful.
     */
    public function mark_failed($chunk_id) {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Mark single chunk as failed.
        return $wpdb->update(
            $wpdb->prefix . 'inqyra_knowledge_chunks',
            [
                'needs_embedding' => 0,
                'embedding_model' => 'failed',
                'updated_at' => current_time('mysql'),
            ],
            ['id' => $chunk_id],
            ['%d', '%s', '%s'],
            ['%d']
        ) !== false;
    }

    /**
     * Get the count of chunks needing embeddings.
     *
     * @param int|null $agent_id Optional agent ID to filter by.
     * @return int Number of chunks needing embeddings.
     */
    public function get_pending_count($agent_id = null) {
        global $wpdb;

        if ($agent_id !== null && $agent_id > 0) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Pending count must be real-time.
            return (int) $wpdb->get_var($wpdb->prepare(
                'SELECT COUNT(*) FROM %i
                 WHERE needs_embedding = 1 AND agent_id = %d',
                $wpdb->prefix . 'inqyra_knowledge_chunks',
                $agent_id
            ));
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name from $wpdb->prefix. Pending count must be real-time.
        return (int) $wpdb->get_var(
            $wpdb->prepare('SELECT COUNT(*) FROM %i WHERE needs_embedding = 1', $wpdb->prefix . 'inqyra_knowledge_chunks')
        );
    }

    /**
     * Get embedding statistics.
     *
     * @param int|null $agent_id Optional agent ID to filter by.
     * @return array Statistics array.
     */
    public function get_statistics($agent_id = null) {
        $vector_store = new Inqyra_Vector_Store();
        $stats = $vector_store->get_statistics($agent_id);

        $stats['model'] = self::MODEL;
        $stats['provider'] = 'browser';

        return $stats;
    }
}
