<?php
/**
 * Vector Storage for Context Awareness.
 *
 * @package Replyan_AI
 */

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

class Replyan_AI_Vector_Storage {

	private string $table_name;

	public function __construct() {
		global $wpdb;
		$this->table_name = $wpdb->prefix . 'replyan_ai_knowledge_chunks';
	}

	/**
	 * Checks if the content hash matches the stored hash for a given post.
	 *
	 * @param int    $post_id
	 * @param string $checksum
	 * @return bool
	 */
	public function is_unchanged( int $post_id, string $checksum ): bool {
		global $wpdb;

		// Get the checksum of the first chunk for this post.
		// Since we delete all chunks and re-insert, the checksum is identical for all chunks of a post.
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$stored_checksum = $wpdb->get_var( $wpdb->prepare( "SELECT checksum FROM {$this->table_name} WHERE post_id = %d LIMIT 1", $post_id ) );

		return $stored_checksum === $checksum;
	}

	/**
	 * Deletes all chunks for a given post.
	 *
	 * @param int $post_id
	 */
	public function delete_chunks( int $post_id ): void {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->delete(
			$this->table_name,
			array( 'post_id' => $post_id ),
			array( '%d' )
		);
	}

	/**
	 * Saves a chunk.
	 *
	 * @param array $data {
	 *     @type int    $post_id
	 *     @type int    $chunk_index
	 *     @type string $post_type
	 *     @type string $title
	 *     @type string $url
	 *     @type string $content
	 *     @type array  $embedding
	 *     @type int    $token_count
	 *     @type string $checksum
	 * }
	 */
	public function save_chunk( array $data ): void {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->insert(
			$this->table_name,
			array(
				'post_id'     => $data['post_id'],
				'chunk_index' => $data['chunk_index'],
				'post_type'   => $data['post_type'],
				'title'       => $data['title'],
				'url'         => $data['url'],
				'content'     => $data['content'],
				'embedding'   => wp_json_encode( $data['embedding'] ),
				'token_count' => $data['token_count'],
				'checksum'    => $data['checksum'],
			),
			array(
				'%d',
				'%d',
				'%s',
				'%s',
				'%s',
				'%s',
				'%s',
				'%d',
				'%s',
			)
		);
	}

	/**
	 * Search for similar chunks using cosine similarity (dot product).
	 *
	 * @param array $query_embedding
	 * @param int   $limit
	 * @return array
	 */
	public function search_similar_chunks( array $query_embedding, int $limit = 5 ): array {
		global $wpdb;

		// Fetch all embeddings to compute similarity in memory
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$rows = $wpdb->get_results( "SELECT id, embedding FROM {$this->table_name}", ARRAY_A );

		if ( empty( $rows ) ) {
			return array();
		}

		$scores = array();

		foreach ( $rows as $row ) {
			$chunk_embedding = json_decode( $row['embedding'], true );
			if ( ! is_array( $chunk_embedding ) ) {
				continue;
			}

			// Calculate dot product (cosine similarity since embeddings are normalized)
			$score = 0;
			$count = min( count( $query_embedding ), count( $chunk_embedding ) );
			for ( $i = 0; $i < $count; $i++ ) {
				$score += $query_embedding[ $i ] * $chunk_embedding[ $i ];
			}

			$scores[ $row['id'] ] = $score;
		}

		arsort( $scores );
		
		$top_ids = array_slice( array_keys( $scores ), 0, $limit );

		if ( empty( $top_ids ) ) {
			return array();
		}

		$ids_placeholder = implode( ',', array_fill( 0, count( $top_ids ), '%d' ) );
		
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$top_chunks = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE id IN ($ids_placeholder)", ...$top_ids ), ARRAY_A );

		// Attach scores and re-sort since IN() doesn't preserve order
		foreach ( $top_chunks as &$chunk ) {
			$chunk['similarity'] = $scores[ $chunk['id'] ];
		}
		
		usort( $top_chunks, function( $a, $b ) {
			return $b['similarity'] <=> $a['similarity'];
		} );

		return $top_chunks;
	}
}
