<?php

namespace ZenoChatbot\Services\Chat\Embedding;

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

/**
 * Vector Math Utilities
 *
 * Provides mathematical operations for embedding vectors,
 * including cosine similarity for semantic matching.
 */
class VectorMath
{
    /**
     * Calculate cosine similarity between two vectors
     *
     * Cosine similarity measures the angle between two vectors,
     * returning a value between -1 (opposite) and 1 (identical).
     * For normalized embeddings, this is equivalent to dot product.
     *
     * @param array $vectorA First embedding vector
     * @param array $vectorB Second embedding vector
     * @return float Similarity score between -1 and 1
     */
    public static function cosineSimilarity(array $vectorA, array $vectorB): float
    {
        if (empty($vectorA) || empty($vectorB)) {
            return 0.0;
        }

        if (count($vectorA) !== count($vectorB)) {
            return 0.0;
        }

        $dotProduct = 0.0;
        $magnitudeA = 0.0;
        $magnitudeB = 0.0;

        $count = count($vectorA);
        for ($i = 0; $i < $count; $i++) {
            $a = (float)$vectorA[$i];
            $b = (float)$vectorB[$i];

            $dotProduct += $a * $b;
            $magnitudeA += $a * $a;
            $magnitudeB += $b * $b;
        }

        $magnitudeA = sqrt($magnitudeA);
        $magnitudeB = sqrt($magnitudeB);

        if ($magnitudeA == 0.0 || $magnitudeB == 0.0) {
            return 0.0;
        }

        return $dotProduct / ($magnitudeA * $magnitudeB);
    }

    /**
     * Calculate Euclidean distance between two vectors
     *
     * @param array $vectorA First embedding vector
     * @param array $vectorB Second embedding vector
     * @return float Distance (lower = more similar)
     */
    public static function euclideanDistance(array $vectorA, array $vectorB): float
    {
        if (empty($vectorA) || empty($vectorB)) {
            return PHP_FLOAT_MAX;
        }

        if (count($vectorA) !== count($vectorB)) {
            return PHP_FLOAT_MAX;
        }

        $sum = 0.0;
        $count = count($vectorA);

        for ($i = 0; $i < $count; $i++) {
            $diff = (float)$vectorA[$i] - (float)$vectorB[$i];
            $sum += $diff * $diff;
        }

        return sqrt($sum);
    }

    /**
     * Normalize a vector to unit length
     *
     * @param array $vector Input vector
     * @return array Normalized vector
     */
    public static function normalize(array $vector): array
    {
        if (empty($vector)) {
            return [];
        }

        $magnitude = 0.0;
        foreach ($vector as $value) {
            $magnitude += (float)$value * (float)$value;
        }
        $magnitude = sqrt($magnitude);

        if ($magnitude == 0.0) {
            return $vector;
        }

        return array_map(fn($v) => (float)$v / $magnitude, $vector);
    }

    /**
     * Find the most similar vectors from a collection
     *
     * @param array $queryVector The vector to compare against
     * @param array $candidates Array of ['id' => ..., 'embedding' => [...]]
     * @param float $threshold Minimum similarity threshold (0-1)
     * @param int $limit Maximum number of results
     * @return array Sorted array of ['id' => ..., 'similarity' => ...]
     */
    public static function findSimilar(
        array $queryVector,
        array $candidates,
        float $threshold = 0.7,
        int $limit = 5
    ): array {
        $results = [];

        foreach ($candidates as $candidate) {
            if (empty($candidate['embedding'])) {
                continue;
            }

            $similarity = self::cosineSimilarity($queryVector, $candidate['embedding']);

            if ($similarity >= $threshold) {
                $results[] = [
                    'id' => $candidate['id'],
                    'similarity' => $similarity,
                    'data' => $candidate['data'] ?? null,
                ];
            }
        }

        // Sort by similarity descending
        usort($results, fn($a, $b) => $b['similarity'] <=> $a['similarity']);

        return array_slice($results, 0, $limit);
    }
}

